自动配置到底配了什么?
对于Spring项目,主要有两种配置
- 一种是类似端口号、数据库地址、用户名密码等
- 一种是各种Bean,比如整合Mybatis需要配置的MapperFactoryBean,比如整合事务配置
SpringBoot中的自动配置,更多的是配置各种Bean,对于第一种配置,比如数据库地址、密码之类的,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到底生不生效,就看具体所指定的条件了。
Starter机制
那SpringBoot中的Starter和自动配置又有什么关系呢?
其实首先要明白一个Starter,就是一个Maven依赖,当我们在项目的pom.xml文件中添加某个Starter 依赖时,其实就是简单的添加了很多其他的依赖,比如:
- spring-boot-starter-web:引入了spring-boot-starter、spring-boot-starter-json、spring-boot-starter-tomcat等和 Web开发相关的依赖包
- spring-boot-starter-tomcat:引入了tomcat-embed-core、tomcat-embed-el、tomcat-embed-websocket等和 Tomcat相关的依赖包
- ...
如果硬要把Starter机制和自动配置联系起来,那就是通过@ConditionalOnClass这个条件注解,因为 这个条件注解的作用就是用来判断当前应用的依赖中是否存在某个类或某些类,比如:
@Configuration(proxyBeanMethods=false) @ConditionalOnClass({Servlet.class,Tomcat.class,UpgradeProtocol.class})
@ConditionalOnMissingBean(value=ServletWebServerFactory.class,search= SearchStrategy.CURRENT)
staticclassEmbeddedTomcat{
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
} }
上面代码中就用到了@ConditionalOnClass,用来判断项目中是否存在Servlet.class、 Tomcat.class、UpgradeProtocol.class这三个类,如果存在就满足当前条件,如果项目中引入了 spring-boot-starter-tomcat,那就有这三个类,如果没有spring-boot-starter-tomcat那就可能没有 这三个类(除非你自己单独引入了Tomcat相关的依赖)。
所以这就做到了,如果我们在项目中要用Tomcat,那就依赖spring-boot-starter-web就够了,因为 它默认依赖了spring-boot-starter-tomcat,从而依赖了Tomcat,从而Tomcat相关的Bean能生效。
而如果不想用Tomcat,那就得这么写:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
得把spring-boot-starter-tomcat给排除掉,再添加上spring-boot-starter-jetty的依赖,这样Tomcat的Bean就不会生效,Jetty的Bean就能生效,从而项目中用的就是Jetty。
@ConditionalOnMissingBean
@ConditionalOnMissingBean的作用是用来判断某个Bean是否缺失,如果不存在某个Bean,那就符 合该条件,理解起来比较简单,但是细细想一下就会存在一个问题,就是顺序问题,还是拿上面的代 码
这个代码中就用到了@ConditionalOnMissingBean,意思是如果当前不存在 ServletWebServerFactory类型的Bean,那就符合条件,结合整体代码,意思就是,:
- 如果用户自己没有定义ServletWebServerFactory类型的Bean,那代码中所定义的Bean就会生效,
- 如果用户自己定义了ServletWebServerFactory类型的Bean,那代码中定义的Bean就不生效 所以这个注解是非常重要的,SpringBoot利用这个注解来决定到底用用户自己的Bean,还是SpringBoot自动配置的。
关键问题在于,不管是自动配置类中定义的Bean,还是用户定义的Bean,都是需要解析的,而且是有 一个顺序的。
如果是:
- 先解析的SpringBoot自动配置类,比如上面的EmbeddedTomcat类
- 再解析程序员定义的Bean
那@ConditionalOnMissingBean的判断结果是有问题的,因为是先解析的EmbeddedTomcat,在解 析的时候是没有发现程序员所定义的Bean的,就会认为符合@ConditionalOnMissingBean的条件, 而实际上程序员是定义了的,只是还没有解析到,所以这就需要SpringBoot把这个顺序控制好,控制 为:
- 先解析用户的定义的Bean,也就是解析用户定义的配置类(包含了扫描和@Bean的解析)
- 再解析SpringBoot中的自动配置类
不管是用户定义的配置类还是自动配置类,都是配置类(简单理解就是加了@Configuration注解)。 SpringBoot启动时,最核心的也就是创建一个Spring容器,而创建Spring容器的过程中会注解做几件 事情:
- 把SpringApplication.run(MyApplication.class)传入进来的MyApplication类做为配置类进行解析
- 由于MyApplication类上定义了@SpringBootApplication,相当于定义@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan注解
- 所以SpringBoot会进一步解析这些注解
- a. @EnableAutoConfiguration:通过@import注解导入AutoConfigurationImportSelector这个配置类,因为它实现了DeferredImportSelector接口,所以Spring会在把其他配置类都解析完之后才解析 AutoConfigurationImportSelector(Spring Framework中的知识)
- b. @ComponentScan:扫描,扫描时会扫描到用户所定义的配置类,并解析用户的配置类,注意:扫描是扫 描不到SpringBoot的自动配置的类,因为扫描的包路径不匹配,SpringBoot的包都是 org.springframework.boot.xxxx,用户都是自己的包路径。
通过上述过程我们发现,Spring会在最后才来解析AutoConfigurationImportSelector这个配置类, 而这个类的作用就是用来解析SpringBoot的自动配置类,那既然无法扫描到SpringBoot中的自动配置 类,那怎么知道SpringBoot中有哪些自动配置类呢,这就需要spring.factories文件,默认情况下, SpringBoot会提供一个spring.factories文件,并把所有自动配置类的名字记录在这个文件中,到时候启动过程中解析这个文件就知道有哪些自动配置类了,并且这件事也是发生在解析完用户的配置类之 后的。
自动配置开启原理
在使用SpringBoot时,通常使用的是@SpringBootApplication这个