前言
为什么我们要用 Spring Boot,Spring Boot 最重要的功能是:自动配置。
为什么说是自动配置?先看@SpringBootApplication注解的源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
Spring Boot 的开启注解是:@SpringBootApplication,从上面的代码可知,其实它就是由下面三个注解组成的:
@Configuration
@ComponentScan
@EnableAutoConfiguration
上面三个注解,前面两个都是 Spring 自带的,和 Spring Boot 无关。而 Spring Boot 最最核心的就是这个 @EnableAutoConfiguration 注解了,它能根据类路径下的 jar 包和配置动态加载配置和注入bean。
举个例子,比如我在 lib 下放一个 druid 连接池的 jar 包,然后在 application.yml 文件配置 druid 相关的参数,Spring Boot 就能够自动配置所有我们需要的东西,如果我把 jar 包拿掉或者把参数去掉,那 Spring Boot 就不会自动配置。
这样我们就能把许多功能做成公共的自动配置的启动器(starters),例如 druid 连接池就是这么做的,它提供了针对 Spring Boot 的启动器:druid-spring-boot-starter。
有了这个自动配置的启动器,我们就能非常简单的使用它,
先添加 jar 包依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
再添加相关参数:
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
……
如果是传统的项目,我们要自己手动写一大堆的配置,而且还不灵活,有了这个启动器,我们就可以做到简单集成。
所以,这才是 Spring Boot 的核心,这才是我们为什么使用 Spring Boot 的原因。
SpringBoot自动配置注解原理解析
前面说过@SpringBootApplication三个比较重要的注解:
@SpringBootConfiguration : Spring Boot的配置类,标注在某个类上,表示这是一个Spring Boot的配置类
@EnableAutoConfiguration: 开启自动配置类,SpringBoot的精华所在。
@ComponentScan包扫描
以前我们需要配置的东西,Spring Boot帮我们自动配置;
@EnableAutoConfiguration告诉SpringBoot开启自动配置功能;这样自动配置才能生效
我们先来看看EnableAutoConfiguration注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
其中,两个比较重要的注解:
@AutoConfigurationPackage:自动配置包
@Import: 导入自动配置的组件
1.AutoConfigurationPackage注解
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
它其实是注册了一个Bean的定义。
*new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的 同级以及子级 * 的包组件。
以上图为例,DemoApplication是和demo包同级,但是demo2这个类是DemoApplication的父级,和example包同级
也就是说,DemoApplication启动加载的Bean中,并不会加载demo2,这也就是为什么,我们要把DemoApplication放在项目的最高级中。
2. Import(AutoConfigurationImportSelector.class)注解:
可以从图中看出 AutoConfigurationImportSelector 继承了DeferredImportSelector 继承了 ImportSelector**
ImportSelector有一个方法为:selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
可以看到第九行,它其实是去加载 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有很多自动配置的类。如下:
如何自定义自己的Bean:
我们以RedisTemplate为例:
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
我们每次在Spring中使用Redis,都会使用到RedisTemplate这个工具类,但是他默认给我们返回的这个工具类,可能不是很符合我们的要求。比如:我们想要开启事务,或者想要改变它默认的序列化。
这时候该如何去做呢?
根据前面的分析,只要我们在容器中放入一个RedisTemplate Bean即可。
@Bean("redisTemplate")
public RedisTemplate<Object, Object> myRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 修改序列化为Jackson
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
// 开启事务
template.setEnableTransactionSupport(true);
return template;
}
我们自己定义我们的RedisTemplate模板,修改序列化,开启事务等操作。
我们将我们自己的Bean加入到IoC容器中以后,他就会默认的覆盖掉原来的RedisTemplate,达到定制的效果。
接下来将在之前的工程的基础上,观察在程序的引导启动过程中,Spring Boot通过自动配置机制帮我们做了哪些工作。
How Do
Spring Boot启动时将自动配置的信息通过DEBUG级别的日志打印到控制台。可以通过设置环境变量(DEBUG)或者程序属性(--debug)设置程序的日志输出级别。
在项目目录下运行
DEBUG=true mvn spring-boot:run
启动应用程序;-
在后台可以看到DEBUG级别的日志输出,在启动日志的最后,可以看到类似AUTO-CONFIGURATION REPORT的字样。
分析
可以看到,后台输出的自动配置信息特别多,好几页屏幕,没办法一一分析,在这里选择一个postive match和negative match进行分析。
Spring Boot通过配置信息指出:特定配置项被选中的原因、列出匹配到对应类的配置项(positive match)、不包括某个配置项的原因(negative match)。现在以DataSourceAutoConfiguration举例说明:
- @ConditionalOnClass 表示对应的类在classpath目录下存在时,才会去解析对应的配置文件,对于DataSourceAutoConfiguration来说就是指:只有javax.sql.DataSource和org.springframwork.jdbc.datasource.embedded.EmbeddedDatabaseType类都能存在时,就会配置对应的数据库资源。
- @ConditionalOnMisssingClass表示对应的类在classpath目录下找不到。
- OnClassCondition用于表示匹配的类型(postive or negative)
OnClassCondition是最普遍的浏览探测条件,除此之外,Spring Boot也使用别的探测条件,如:OnBeanCondition用于检测指定bean实例存在与否、OnPropertyCondition用于检查指定属性是否存在等等。
符合negative match代表一些配置类(xxxConfiguration之类的),它们虽然存在于classpath目录,但是修饰它们的注解中依赖的其他类不存在。导入如果在pom文件中导入spring-boot-autoconfigure包,则GsonAutoConfiguration就会出现在classpath目录下,但是该配置类被@ConditionalOnClass(Gson.class)
修饰,而com.google.gson.Gson类不在classpath目录。
@Configuration
@ConditionalOnClass({Gson.class})
public class GsonAutoConfiguration {
public GsonAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public Gson gson() {
return new Gson();
}
}
总结
- @ConditionalOnClass:该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类;
- @ConditionalOnMissingBean:该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean;可以给该注解传入参数例如
@ConditionOnMissingBean(name = "example")
,这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行。