对于一个Spring项目,主要就是有两种配置:
SpringBoot中的自动配置,更多的是配置各种Bean,因为对于第一种配置,SpringBoot也无法去配置,比如数据库地址、密码之类的,SpringBoot肯定是无法知道的,但是对于端口号这些配置,SpringBoot也是会提供一种默认值的,也相当于一种自动配置。
那SpringBoot是如何自动的帮助我们来配置这些Bean的呢?并且如果某些Bean程序员自己也配置了,那SpringBoot是如何进行选择的呢?
SpringBoot要自动帮我们配置Bean,那要支持的就多了:
所以SpringBoot中肯定不能把所有的Bean都定义在一个配置类中,需要分门别类,这样就针对不同的场景定义了不同的自动配置类,比如:
使用这种结构后,SpringBoot就能让程序员更为方便的来控制某个Bean或某些Bean要不要生效,如果某个自动配置类不生效,那该配置类中所定义的Bean则都不会生效。
那SpringBoot中是如何控制某个自动配置类或某个Bean生不生效呢?
SpringBoot中的条件注解有:
当然我们也可以利用@Conditional来自定义条件注解。
条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不会生效就要看当前条件能不能符合,或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。
具体原理是:
我们可以发现,SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。
那SpringBoot中的Starter和自动配置又有什么关系呢?
其实首先要明白一个Starter,就是一个Maven依赖,当我们在项目的pom.xml文件中添加某个Starter依赖时,其实就是简单的添加了很多其他的依赖,比如:
如果硬要把Starter机制和自动配置联系起来,那就是通过@ConditionalOnClass这个条件注解,因为这个条件注解的作用就是用来判断当前应用的依赖中是否存在某个类或某些类,比如:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
// orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,默认是没有的,程序员可以自己定义
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.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
得把spring-boot-starter-tomcat给排除掉,再添加上spring-boot-starter-jetty的依赖,这样Tomcat的Bean就不会生效,Jetty的Bean就能生效,从而项目中用的就是Jetty。
@ConditionalOnMissingBean的作用是用来判断某个Bean是否缺失,如果不存在某个Bean,那就符合该条件,理解起来比较简单,但是细细想一下就会存在一个问题,就是顺序问题,还是拿上面的代码举例:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
// orderedStream()调用时会去Spring容器中找到TomcatConnectorCustomizer类型的Bean,默认是没有的,程序员可以自己定义
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;
}
}
这个代码中就用到了@ConditionalOnMissingBean,意思是如果当前不存在ServletWebServerFactory类型的Bean,那就符合条件,结合整体代码,意思就是,:
所以这个注解是非常重要的,SpringBoot利用这个注解来决定到底用用户自己的Bean,还是用SpringBoot自动配置的。
关键问题在于,不管是自动配置类中定义的Bean,还是用户定义的Bean,都是需要解析的,而且是有一个顺序的。
如果是:
那@ConditionalOnMissingBean的判断结果是有问题的,因为是先解析的EmbeddedTomcat,在解析的时候是没有发现程序员所定义的Bean的,就会认为符合@ConditionalOnMissingBean的条件,而实际上程序员是定义了的,只是还没有解析到,所以这就需要SpringBoot把这个顺序控制好,控制为:
不管是用户定义的配置类还是自动配置类,都是配置类(简单理解就是加了@Configuration注解)。SpringBoot启动时,最核心的也就是创建一个Spring容器,而创建Spring容器的过程中会注解做几件事情:
通过上述过程我们发现,Spring会在最后才来解析AutoConfigurationImportSelector这个配置类,而这个类的作用就是用来解析SpringBoot的自动配置类,那既然无法扫描到SpringBoot中的自动配置类,那怎么知道SpringBoot中有哪些自动配置类呢,这就需要spring.factories文件,默认情况下,SpringBoot会提供一个spring.factories文件,并把所有自动配置类的名字记录在这个文件中,到时候启动过程中解析这个文件就知道有哪些自动配置类了,并且这件事也是发生在解析完用户的配置类之后的。
我们在使用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 @interface SpringBootApplication {
// ...
}
可以发现这个注解上有另外三个注解:
所以我们可以认为@SpringBootApplication是一个三合一注解,也就是我们也可以这么用:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {
public static void main(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 @interface EnableAutoConfiguration {
}
其中非常核心的就是:
@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector实现了DeferredImportSelector这个接口,Spring容器在启动时,会在解析完其他所有程序员定义的配置类之后,来调用AutoConfigurationImportSelector中的selectImports方法,然后把该方法返回的类名对应的类作为配置类进行解析。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 查找自动配置类
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
该方法会利用SpringFactoriesLoader找到所有的META-INF/spring.factories文件中key为EnableAutoConfiguration.class的value值,也就是众多自动配置类的类名。拿到这些类名后会进行去重,去重完之后,就会看是否存在某些自动配置类需要排除,我们可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude配置来指定一些自动配置类的名字,然后把它们从自动配置类集合中排除掉。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 扫描 spring.factories
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 去除排除的自动配置类
configurations.removeAll(exclusions);
// 使用AutoConfigurationImportFilter进一步进行筛选
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
然后会继续利用ConfigurationClassFilter对自动配置类进行进一步筛选,ConfigurationClassFilter会利用AutoConfigurationMetadata进行筛选,而AutoConfigurationMetadata对象对应的是"META-INF/spring-autoconfigure-metadata.properties"文件中的内容,这是一种加快SpringBoot启动速度的机制,默认是开启了的(不过要通过maven或gradle的方式引入springboot的依赖来使用才能看到效果,因为这个文件的内容是在SpringBoot源码工程编译的时候自动生成出来的,当然我们也可以手动创建这个文件,以及这个文件的内容),自动生成的这个文件内容样例:
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration.ConditionalOnBean=org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthContributorAutoConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration.ConditionalOnBean=org.springframework.boot.actuate.audit.AuditEventRepository
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration
org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.availability.AvailabilityProbesAutoConfiguration=
org.springframework.boot.actuate.autoconfigure.availability.AvailabilityProbesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.actuate.autoconfigure.availability.AvailabilityHealthContributorAutoConfiguration,org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration=
内容格式为:自动配置类名.条件注解=条件
有了这个文件的内容,SpringBoot会在通过spring.facotries文件找到所有的自动配置类后,会把这个文件中的内容读出来,然后利用AutoConfigurationImportFilter对所有的自动配置类进行条件匹配,这里的条件判断,只会判断所需要的类是否存在,如果需要的类,或者需要的Bean对应的类,都不存在,那么肯定不符合条件了,对于像@ConditionalOnMissingBean这样的条件,在这一步是不会去判断的,最后条件匹配成功的自动配置类就会记录下来,并最终返回给Spring容器,继续进行其他条件的匹配。
所以通过这个机制,使得Spring并不需要解析所有的自动配置类,从而提高了效率。
当然在这个过滤的过程中,如果日志级别等于trace级别,那么会把所有条件不匹配的自动配置类记录到日志中,如果日志框架配置了打印到控制台,那就会打印到控制台,比如:
logging.level.root=trace
日志如下:
2022-09-05 10:16:21.328 TRACE 3704 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'autoConfigurationReport'
2022-09-05 10:16:21.442 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'com.rabbitmq.client.Channel'
2022-09-05 10:16:21.447 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'org.springframework.batch.core.launch.JobLauncher'
2022-09-05 10:16:21.447 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'com.datastax.oss.driver.api.core.CqlSession'
2022-09-05 10:16:21.447 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'com.couchbase.client.java.Cluster'
2022-09-05 10:16:21.448 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor'
2022-09-05 10:16:21.448 TRACE 3704 --- [ main] o.s.b.a.condition.OnClassCondition : Condition OnClassCondition on org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration did not match due to @ConditionalOnClass did not find required class 'com.datastax.oss.driver.api.core.CqlSession'
在SpringBoot中,还有一个更加强大的统计自动配置类匹配结果的功能,就是可以配置debug=true,只要开启了这个配置,那么Spring在解析每一个自动配置类时,就会将是否匹配的结果进行记录,最后再一起打印出来,上面的是每解析一个就打印一个。
比如开启了debug=true,我们可以在控制台看到:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)
AopAutoConfiguration.ClassProxyingConfiguration matched:
- @ConditionalOnMissingClass did not find unwanted class 'org.aspectj.weaver.Advice' (OnClassCondition)
- @ConditionalOnProperty (spring.aop.proxy-target-class=true) matched (OnPropertyCondition)
DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)
DispatcherServletAutoConfiguration.DispatcherServletConfiguration matched:
- @ConditionalOnClass found required class 'javax.servlet.ServletRegistration' (OnClassCondition)
- Default DispatcherServlet did not find dispatcher servlet beans (DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition)
可以看到这个匹配结果分别记录了:
这个功能的实现,是Spring解析具体的自动配置类上的各种条件注解的时候统计的,每解析一个条件注解,就会把结果记录在ConditionEvaluationReport对象中,当Spring容器启动完成后,会发布一个ContextRefreshedEvent事件,而SpringBoot提供了一个ConditionEvaluationReportLoggingListener会处理这个事件,接收到这个事件后就会把统计结果进行打印。
自动配置类解析的大体流程为: