自动配置到底配了什么?
对于一个Spring项目,主要就是有两种配置:
-
一种是类似端口号、数据库地址、用户名密码等
-
一种是各种Bean,比如整合Mybatis需要配置的MapperFactoryBean,比如整合事务需要配置DataSourceTransactionManager
SpringBoot中的自动配置,更多的是配置各种Bean,因为对于第一种配置,SpringBoot也无法去配置,比如数据库地址、密码之类的,SpringBoot肯定是无法知道的,但是对于端口号这些配置,SpringBoot也是会提供一种默认值的,也相当于一种自动配置。
那SpringBoot是如何自动的帮助我们来配置这些Bean的呢?并且如果某些Bean程序员自己也配置了,那SpringBoot是如何进行选择的呢?
自动配置类
SpringBoot要自动帮我们配置Bean,那要支持的就多了:
-
比如Spring整合各种Servlet容器(Tomcat、Jetty)的Bean
-
比如Spring整合各种消息队列的Bean
-
等等
所以SpringBoot中肯定不能把所有的Bean都定义在一个配置类中,需要分门别类,这样就针对不同的场景定义了不同的自动配置类,比如:
-
ServletWebServerFactoryAutoConfiguration:配置了Servlet Web场景中所需要的一些Bean
-
TransactionAutoConfiguration:配置了事务场景中所需要的一些Bean
-
AopAutoConfiguration:配置了AOP场景中所需要的一些Bean
-
RabbitAutoConfiguration:配置了Rabbitmq场景中所需要的一些Bean
-
等等
使用这种结构后,SpringBoot就能让程序员更为方便的来控制某个Bean或某些Bean要不要生效,如果某个自动配置类不生效,那该配置类中所定义的Bean则都不会生效。
那SpringBoot中是如何控制某个自动配置类或某个Bean生不生效呢?
条件注解
SpringBoot中的条件注解有:
-
ConditionalOnBean:是否存在某个某类或某个名字的Bean
-
ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
-
ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
-
ConditionalOnClass:是否存在某个类
-
ConditionalOnMissingClass:是否缺失某个类
-
ConditionalOnExpression:指定的表达式返回的是true还是false
-
ConditionalOnJava:判断Java版本
-
ConditionalOnJndi:JNDI指定的资源是否存在
-
ConditionalOnWebApplication:当前应用是一个Web应用
-
ConditionalOnNotWebApplication:当前应用不是一个Web应用
-
ConditionalOnProperty:Environment中是否存在某个属性
-
ConditionalOnResource:指定的资源是否存在
-
ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行
-
ConditionalOnCloudPlatform:是不是在某个云平台上
当然我们也可以利用@Conditional来自定义条件注解。
条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不会生效就要看当前条件能不能符合,或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。
具体原理是:
-
Spring在解析某个自动配置类时,会先检查该自动配置类上是否有条件注解,如果有,则进一步判断该条件注解所指定的条件当前能不能满足,如果满足了则继续解析该配置类,如果不满足则不进行解析了,也就是配置类所定义的Bean都得不到解析,也就是相当于没有这些Bean了。
-
同理,Spring在解析某个@Bean的方法时,也会先判断方法上是否有条件注解,然后进行解析,如果不满足条件,则该Bean不会生效
我们可以发现,SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。
自动配置开启原理
我们在使用SpringBoot时,通常使用的是@SpringBootApplication这个注解,比如:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class);
}
}
而这个注解的定义为:
@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@interfaceSpringBootApplication {
// ...
}
可以发现这个注解上有另外三个注解:
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
所以我们可以认为@SpringBootApplication是一个三合一注解,也就是我们也可以这么用:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
publicclassMyApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(MyApplication.class);
}
}
如果我们这么用就能自己控制要不要用@EnableAutoConfiguration这个注解,如果用就表示开启自动配置,如果不用就表示不开启自动配置,那开启和不开启自动配置到底该怎么理解呢?
我们前面分析过,SpringBoot的自动配置就是SpringBoot帮助程序员配置一些Bean,从而让程序员在用SpringBoot时可以少去配置很多Bean,所以如果我们开启了自动配置,那最终Spring容器中就有SpringBoot帮我们配置的Bean,如果没有开启自动配置,那Spring容器中就没有这些Bean,就需要程序员去配置。
那我们来看看@EnableAutoConfiguration这个注解是如何工作的?先看源码定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public@interfaceEnableAutoConfiguration {
}
其中非常核心的就是:
@Import(AutoConfigurationImportSelector.class)
而AutoConfigurationImportSelector实现了DeferredImportSelector这个接口,Spring容器在启动时,会在解析完其他所有程序员定义的配置类之后,来调用AutoConfigurationImportSelector中的selectImports方法,然后把该方法返回的类名对应的类作为配置类进行解析。
该方法会利用SpringFactoriesLoader找到所有的META-INF/spring.factories文件中key为EnableAutoConfiguration.class的value值,也就是众多自动配置类的类名。
拿到这些类名后会进行去重,去重的代码为:
newArrayList<>(newLinkedHashSet<>(list))
去重完之后,就会看是否存在某些自动配置类需要排除,我们可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude配置来指定一些自动配置类的名字,然后把它们从自动配置类集合中排除掉。
然后会继续利用ConfigurationClassFilter对自动配置类进行进一步筛选,ConfigurationClassFilter会利用AutoConfigurationMetadata进行筛选,而AutoConfigurationMetadata对象对应的是"META-INF/spring-autoconfigure-metadata.properties"文件中的内容,这是一种加快SpringBoot启动速度的机制,默认是开启了的(不过要通过maven或gradle的方式引入springboot的依赖来使用才能看到效果,因为这个文件的内容是在SpringBoot源码工程编译的时候自动生成出来的,当然我们也可以手动创建这个文件,以及这个文件的内容),自动生成的这个文件内容样例:
![](https://i-blog.csdnimg.cn/blog_migrate/cc8b2210950d583a4d2f17188b4fb6dd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/881c353d9fcfa4b659f0e40d4033ff34.png)
内容格式为:自动配置类名.条件注解=条件
有了这个文件的内容,SpringBoot会在通过spring.facotries文件找到所有的自动配置类后,会把这个文件中的内容读出来,然后利用AutoConfigurationImportFilter对所有的自动配置类进行条件匹配,这里的条件判断,只会判断所需要的类是否存在,如果需要的类,或者需要的Bean对应的类,都不存在,那么肯定不符合条件了,对于像@ConditionalOnMissingBean这样的条件,在这一步是不会去判断的,最后条件匹配成功的自动配置类就会记录下来,并最终返回给Spring容器,继续进行其他条件的匹配。
所以通过这个机制,使得Spring并不需要解析所有的自动配置类,从而提高了效率。
自动配置类解析的大体流程为:
-
读取spring.factories中的所有自动配置类
-
看是否配置了需要排除的自动配置类,进行排除
-
然后利用spring-autoconfigure-metadata.properties文件来过滤掉一些自动配置类(条件中指定的类不存在的自动配置类)
-
解析过滤后自动配置类,判断自动配置类所有的条件注解,条件全部符合才会真正去解析自动配置类上的其他内容,比如@Bean(也会进行条件判断)