Fork me on GitHub

Why is Spring Boot? --Spring Boot 四大神器

spring-boot相关具体示例代码,可以查看GitHub中spring-boot-demo

起步依赖 spring-boot-starter-xx

向项目中添加依赖是件富有挑战的事,需要什么库,哪个版本?它的Group和Artifact是什么,具体依赖的版本和项目中的其他依赖是否会发生冲突?

Spring boot 通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖(or Gradle),利用传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖

举个栗子,假设你正在用Spring MVC构造一个REST API,并将JSON (JavaScript Object Notation)作为资源表述。此外,你还想运用遵循JSR-303 规范的声明式校验,并使用嵌入式的Tomcat 服务器来提供服务。要实现以上目标,你在Maven或Gradle里至少需要以下8个依赖:

  • org.springframework:spring-core
  • org.springframework:spring-web
  • org.springframework:spring-webmvc
  • com.fasterxml.jackson.core:jackson-databind
  • org.hibernate:hibernate-validator
  • org.apache.tomcat.embed:tomcat-embed-core
  • org.apache.tomcat.embed:tomcat-embed-el
  • org.apache.tomcat.embed:tomcat-embed-logging-juli

再说了,你怎么知道这个列表是完整的?在一行代码都没写的情况下,离开始构建还有很长的路要走。

不过,如果打算利用Spring Boot的起步依赖,你只需添加Spring Boot的Web 起步依赖(org.springframework.boot:spring-boot-starter-web ),仅此一个。它会根据依赖传递把其他所需依赖引入项目里,你都不用考虑它们。

比起减少依赖数量,起步依赖还引入了一些微妙的变化。向项目中添加了Web起步依赖,实际上指定了应用程序所需的一类功能,这就是指定基于功能的依赖。因为应用是个Web应用程序,所以加入了Web起步依赖。

与之类似,如果应用程序要用到JPA 持久化,那么就可以加入jpa起步依赖。如果需要安全功能,那就加入security起步依赖。简而言之,不再需要考虑支持某种功能要用什么库了,引入相关起步依赖就行

此外,Spring Boot 的起步依赖还把你从“需要这些库的哪些版本”这个问题里解放了出来。起步依赖引入的库的版本都是经过测试的,因此你可以完全放心,它们之间不会出现不兼容的情况。

AutoConfig

Spring Boot 的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring 配置应该用哪个,不该用哪个。

举个栗子,Spring Boot 自动配置要考虑的:

  • Spring 的JdbcTemplate是不是在Classpath 里?如果是,并且有DataSource的Bean,则自动配置一个JdbcTemplate的Bean。
  • Thymeleaf是不是在Classpath 里?如果是,则配置Thymeleaf的模板解析器、视图解析器以及模板引擎。
  • Spring Security是不是在Classpath 里?如果是,则进行一个非常基本的Web安全设置。

每当应用程序启动的时候,Spring Boot 的自动配置都要做将近200个这样的决定,涵盖安全、集成、持久化、Web开发等诸多方面。所有这些自动配置就是为了尽量不让使用者自己写配置。

配置是Spring Framework的核心元素,必须要有东西告诉Spring如何运行应用程序。

在向应用程序加入Spring Boot 时,有个名为spring-boot-autoconfigure 的JAR 文件,其中包含了很多配置类。每个配置类都在应用程序的Classpath 里,都有机会为应用程序的配置添砖加瓦。这些配置类里有用于Thymeleaf的配置,有用于Spring Data JPA的配置,有用于Spiring MVC的配置,还有很多其他东西的配置,你可以自己选择是否在Spring 应用程序里使用它们。

所有这些配置如此与众不同,原因在于它们利用了Spring 的条件化配置,这是Spring 4.0引入的新特性条件化配置允许配置存在于应用程序中,但在满足某些特定条件之前都忽略这个配置

在Spring 里可以很方便地编写你自己的条件,你所要做的就是实现Condition接口,覆盖它的matches()方法。举例来说,下面这个简单的条件类只有在Classpath 里存在JdbcTemplate时才会生效:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
try {
context.getClassLoader().loadClass(
"org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (Exception e) {
return false;
}
}
}

当你用Java 来声明Bean的时候,可以使用这个自定义条件类:

1
2
3
4
@Conditional(JdbcTemplateCondition.class)
public MyService myService() {
...
}

在这个例子里,只有当JdbcTemplateCondition 类的条件成立时才会创建MyService这个Bean。也就是MyService Bean 创建的条件是Classpath 里有JdbcTemplate。否则,这个Bean的声明就会被忽略掉。

@EnableAutoConfiguration自动配置原理

通过@EnableAutoConfiguration启用Spring应用程序上下文的自动配置,这个注解会导入一个EnableAutoConfigurationImportSelector的类,而这个类会去读取一个spring.factories下key为EnableAutoConfiguration对应的全限定名的值。

这个spring.factories里面配置的那些类,主要作用是告诉Spring Boot这个stareter所需要加载的那些xxxAutoConfiguration类,也就是你真正的要自动注册的那些bean或功能。然后,我们实现一个spring.factories指定的类,标上@Configuration注解,一个starter就定义完了。

如果想从自己的starter种读取应用的starter工程的配置,只需要在入口类上加上如下注解即可:

1
@EnableConfigurationProperties(MyProperties.class)

读取spring.factories文件的实现

是通过org.springframework.core.io.support.SpringFactoriesLoader实现。

SpringFactoriesLoader的实现类似于SPI(Service Provider Interface,在java.util.ServiceLoader的文档里有比较详细的介绍。java SPI提供一种服务发现机制,为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要[3])。

SpringFactoriesLoader会加载classpath下所有JAR文件里面的META-INF/spring.factories文件。

其中加载spring.factories文件的代码在loadFactoryNames方法里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
....
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

通过org.springframework.boot.autoconfigure.AutoConfigurationImportSelector里面的getCandidateConfigurations方法,获取到候选类的名字List。该方法代码如下:

1
2
3
4
5
6
7
8
9
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

其中,getSpringFactoriesLoaderFactoryClass()方法直接返回的是EnableAutoConfiguration.class, 代码如下:

1
2
3
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}

所以,getCandidateConfigurations方法里面的这段代码:

1
2
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名对应的值。全限定名都使用如下命名方法:

1
2
3
4
5
6
包名.外部类名
包名.外部类名$内部类名
e.g:
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

SpringBoot中的META-INF/spring.factories(完整路径:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中关于EnableAutoConfiguration的这段配置如下:

1
2
3
4
5
6
7
8
9
10
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
...

当然了,这些AutoConfiguration不是所有都会加载的,会根据AutoConfiguration上的@ConditionalOnClass等条件,再进一步判断是否加载。

条件化注解配置生效条件
@ConditionalOnBean配置了某个特定Bean
@ConditionalOnMissingBean没有配置特定的Bean
@ConditionalOnClassClasspath 里有指定的类
@ConditionalOnMissingClassClasspath 里缺少指定的类
@ConditionalOnExpression给定的SpEL表达式计算结果为true
@ConditionalOnJavaJava 的版本匹配特定值或者一个范围值
@ConditionalOnJndi参数中给定的JNDI位置必须存在一个
@ConditionalOnProperty指定的配置属性要有一个明确的值
@ConditionalOnResourceClasspath 里有指定的资源
@ConditionalOnWebApplication这是一个Web应用程序
@ConditionalOnNotWebApplication这不是一个Web应用程序

spring boot自动配置构建于spring的条件化配置之上,提供了众多带有@Conditional注解的配置类,根据条件决定是否要自动配置这些Bean。

DataSourceAutoConfiguration里嵌入了一个JdbcTemplateConfiguration类,自动配置了一个JdbcTemplate Bean :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
@Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
protected static class JdbcTemplateConfiguration {
@Autowired(required = false)
private DataSource dataSource;
@Bean
@ConditionalOnMissingBean(JdbcOperations.class)
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
...
}

JdbcTemplateConfiguration 使用了@Conditional注解,判断DataSourceAvailable- Condition条件是否成立——基本上就是要有一个DataSource Bean 或者要自动配置创建一个。假设有DataSource Bean ,使用了@Bean注解的jdbcTemplate() 方法会配置一个JdbcTemplate Bean 。这个方法上还加了@ConditionalOnMissingBean注解,因此只有在不存在JdbcOperations(即JdbcTemplate实现的接口)类型的Bean时,才会创建JdbcTemplate Bean。

自动配置会做出以下配置决策,它们和之前的例子息息相关。

  • 因为Classpath 里有Hibernate (Spring Data JPA 传递引入的)的实体管理器,所以自动配置会配置与Hibernate 相关的Bean,包括Spring 的LocalContainerEntityManager- FactoryBean 和JpaVendorAdapter。
  • 因为Classpath 里有Spring Data JPA ,所以它会自动配置为根据仓库的接口创建仓库实现。
  • 因为Classpath 里有Thymeleaf,所以Thymeleaf会配置为Spring MVC的视图,包括一个Thymeleaf的模板解析器、模板引擎及视图解析器。视图解析器会解析相对于Classpath 根目录的/templates目录里的模板。
  • 因为Classpath 里有Tomcat (通过Web起步依赖传递引用),所以会启动一个嵌入式的Tomcat容器,监听8080 端口。

由此可见,Spring Boot 自动配置承担起了配置Spring 的重任,因此你能专注于编写自己的应用程序。

命令行界面

这是Spring Boot的可选特性,借此你只需写代码就能完成完整的应用程序,无需传统项目构建。

创建一个文件 app.groovy:

1
2
3
4
5
6
7
@RestController
class ThisWillActuallyRun {
@RequestMapping("/")
String home() {
"Hello World!"
}
}

然后执行shell命令:

1
$ spring run app.groovy

在浏览器中打开 localhost:8080 预览结果:

1
Hello World!

Spring Boot CLI让只写代码即可实现应用程序成为可能。Spring Boot CLI 利用了起步依赖和自动配置,让你专注于代码本身。不仅如此,你是否注意代码里没有import ?CLI如何知道RequestMapping和RestController来自哪个包呢?说到这个问题,那些类最终又是怎么跑到Classpath 里的呢?

说得简单一点,CLI能检测到你使用了哪些类,它知道要向Classpath 中添加哪些起步依赖才能让它运转起来。一旦那些依赖出现在Classpath 中,一系列自动配置就会接踵而来,确保启用DispatcherServlet和Spring MVC,这样控制器就能响应HTTP请求了。

Spring Boot CLI 是Spring Boot 的非必要组成部分。虽然它为Spring 带来了惊人的力量,大大简化了开发,但也引入了一套不太常规的开发模型。要是这种开发模型与你的口味相去甚远,那也没关系,抛开CLI,你还是可以利用Spring Boot 提供的其他东西。

Actuator

Spring Boot的最后一块“拼图”是Actuator,其他几个部分旨在简化Spring 开发,而Actuator则要提供在运行时检视应用程序内部情况的能力。安装了Actuator就能窥探应用程序的内部情况了,包括如下细节:

  • Spring 应用程序上下文里配置的Bean
  • Spring Boot 的自动配置做的决策
  • 应用程序取到的环境变量、系统属性、配置属性和命令行参数
  • 应用程序里线程的当前状态
  • 应用程序最近处理过的HTTP请求的追踪情况
  • 各种和内存用量、垃圾回收、Web请求以及数据源用量相关的指标

Actuator通过Web端点和shell 界面向外界提供信息。如果要借助shell 界面,你可以打开SSH(Secure Shell),登入运行中的应用程序,发送指令查看它的情况。

总结

从本质上来说,Spring Boot就是Spring,它做了那些没有它你自己也会去做的SpringBean配置。谢天谢地,幸好有Spring Boot,你不用再写这些样板配置了,可以专注于应用程序的逻辑,这些才是应用程序独一无二的东西。

------------------------------------本文结束感谢阅读------------------------------------
坚持技术分享,你的支持将鼓励我继续创作!
0%