赞
踩
对于一个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.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>
@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,都是需要解析的,而且是有一个顺序的。
如果是,先解析的SpringBoot自动配置类,比如上面的EmbeddedTomcat类
再解析程序员定义的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方法,然后把该方法返回的类名对应的类作为配置类进行解析。
该方法会利用SpringFactoriesLoader找到所有的META-INF/spring.factories文件中key为EnableAutoConfiguration.class的value值,也就是众多自动配置类的类名。
拿到这些类名后会进行去重,去重的代码为
new ArrayList<>(new LinkedHashSet<>(list))
去重完之后,就会看是否存在某些自动配置类需要排除,我们可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude配置来指定一些自动配置类的名字,然后把它们从自动配置类集合中排除掉。
然后会继续利用ConfigurationClassFilter对自动配置类进行进一步筛选,ConfigurationClassFilter会利用AutoConfigurationMetadata进行筛选,而AutoConfigurationMetadata对象对应的是"META-INF/spring-autoconfigure-metadata.properties"文件中的内容,这是一种加快SpringBoot启动速度的机制,默认是开启了的(不过要通过maven或gradle的方式引入springboot的依赖来使用才能看到效果,因为这个文件的内容是在SpringBoot源码工程编译的时候自动生成出来的,当然我们也可以手动创建这个文件,以及这个文件的内容),自动生成的这个文件内容样例
内容格式为:自动配置类名.条件注解=条件
有了这个文件的内容,SpringBoot会在通过spring.facotries文件找到所有的自动配置类后,会把这个文件中的内容读出来,然后利用AutoConfigurationImportFilter对所有的自动配置类进行条件匹配,这里的条件判断,只会判断所需要的类是否存在,如果需要的类,或者需要的Bean对应的类,都不存在,那么肯定不符合条件了,对于像@ConditionalOnMissingBean这样的条件,在这一步是不会去判断的,最后条件匹配成功的自动配置类就会记录下来,并最终返回给Spring容器,继续进行其他条件的匹配。
所以通过这个机制,使得Spring并不需要解析所有的自动配置类,从而提高了效率。
当然在这个过滤的过程中,如果日志级别等于trace级别,那么会把所有条件不匹配的自动配置类记录到日志中,如果日志框架配置了打印到控制台,那就会打印到控制台,比如
在SpringBoot中,还有一个更加强大的统计自动配置类匹配结果的功能,就是可以配置debug=true,只要开启了这个配置,那么Spring在解析每一个自动配置类时,就会将是否匹配的结果进行记录,比如开启了debug=true,我们可以在控制台看到
可以看到这个匹配结果分别记录了:
这个功能的实现,是Spring解析具体的自动配置类上的各种条件注解的时候统计的,每解析一个条件注解,就会把结果记录在ConditionEvaluationReport对象中,当Spring容器启动完成后,会发布一个ContextRefreshedEvent事件,而SpringBoot提供了一个ConditionEvaluationReportLoggingListener会处理这个事件,接收到这个事件后就会把统计结果进行打印。
自动配置类解析的大体流程为:
通过前面我们会SpringBoot的自动配置机制、Starter机制、启动过程的底层分析,我们拿一个实际的业务案例来串讲一下,那就是SpringBoot和Tomcat的整合。
我们知道,只要我们的项目添加的starter为:spring-boot-starter-web,那么我们启动项目时,SpringBoot就会自动启动一个Tomcat。
那么这是怎么做到的呢?
首先我们可以发现,在spring-boot-starter-web这个starter中,其实简介的引入了spring-boot-starter-tomcat这个starter,这个spring-boot-starter-tomcat又引入了tomcat-embed-core依赖,所以只要我们项目中依赖了spring-boot-starter-web就相当于依赖了Tomcat。
然后在SpringBoot众多的自动配置类中,有一个自动配置类叫做ServletWebServerFactoryAutoConfiguration,定义为
- @Configuration(proxyBeanMethods = false)
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
- @ConditionalOnClass(ServletRequest.class)
- @ConditionalOnWebApplication(type = Type.SERVLET)
- @EnableConfigurationProperties(ServerProperties.class)
- @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
- ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
- ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
- ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
- public class ServletWebServerFactoryAutoConfiguration {
- // ...
- }
首先看这个自动配置类所需要的条件
在上面提到的spring-boot-starter-web中,其实还间接的引入了spring-web、spring-webmvc等依赖,这就使得第二个条件满足,而对于第一个条件的ServletRequest类,虽然它是Servlet规范中的类,但是在我们所依赖的tomcat-embed-core这个jar包中是存在这个类的,这是因为Tomcat在自己的源码中把Servlet规范中的一些代码也包含进去了,比如
这就使得ServletWebServerFactoryAutoConfiguration这个自动配置的两个条件都符合,那么Spring就能去解析它,一解析它就发现这个自动配置类Import进来了三个类
很明显,Import进来的这三个类应该是差不多,我们看EmbeddedTomcat这个类
- @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;
- }
-
- }
可以发现这个类是一个配置类,所以Spring也会来解析它,不过它也有两个条件:
所以,通常只要我们项目依赖中有Tomcat依赖,那就符合条件,那最终Spring容器中就会有TomcatServletWebServerFactory这个Bean。
对于另外的EmbeddedJetty和EmbeddedUndertow,也差不多,都是判断项目依赖中是否有Jetty和Undertow的依赖,如果有,那么对应在Spring容器中就会存在JettyServletWebServerFactory类型的Bean、或者存在UndertowServletWebServerFactory类型的Bean。
总结一下:
那么SpringBoot给我们配置的这几个Bean到底有什么用呢?
我们前面说到,TomcatServletWebServerFactory实现了ServletWebServerFactory这个接口,这个接口的定义为
- public interface ServletWebServerFactory {
- WebServer getWebServer(ServletContextInitializer... initializers);
- }
- public interface WebServer {
- void start() throws WebServerException;
- void stop() throws WebServerException;
- int getPort();
- }
我们发现ServletWebServerFactory其实就是用来获得WebServer对象的,而WebServer拥有启动、停止、获取端口等方法,那么很自然,我们就发现WebServer其实指的就是Tomcat、Jetty、Undertow,而TomcatServletWebServerFactory就是用来生成Tomcat所对应的WebServer对象,具体一点就是TomcatWebServer对象,并且在生成TomcatWebServer对象时会把Tomcat给启动起来,在源码中,调用TomcatServletWebServerFactory对象的getWebServer()方法时就会启动Tomcat。
我们再来看TomcatServletWebServerFactory这个Bean的定义
- @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;
- }
要构造这个Bean,Spring会从Spring容器中获取到TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer这三个类型的Bean,然后把它们添加到TomcatServletWebServerFactory对象中去,很明显这三种Bean是用来配置Tomcat的,比如
也就是我们可以通过定义TomcatConnectorCustomizer类型的Bean,来对Tomcat进行配置,比如
- @SpringBootApplication
- public class MyApplication {
-
- @Bean
- public TomcatConnectorCustomizer tomcatConnectorCustomizer(){
- return new TomcatConnectorCustomizer() {
- @Override
- public void customize(Connector connector) {
- connector.setPort(8888);
- }
- };
- }
-
- public static void main(String[] args) {
- SpringApplication.run(MyApplication.class);
- }
-
- }
有了TomcatServletWebServerFactory这个Bean之后,在SpringBoot的启动过程中,会执行ServletWebServerApplicationContext的onRefresh()方法,而这个方法会调用createWebServer()方法,而这个方法中最为重要的两行代码为
- ServletWebServerFactory factory = getWebServerFactory();
- this.webServer = factory.getWebServer(getSelfInitializer());
很明显,getWebServerFactory()负责获取具体的ServletWebServerFactory对象,要么是TomcatServletWebServerFactory对象,要么是JettyServletWebServerFactory对象,要么是UndertowServletWebServerFactory对象,注意只能获取到一个,然后调用该对象的getWebServer方法,启动对应的Tomcat、或者Jetty、或者Undertow。
getWebServerFactory方法中的逻辑比较简单,获取Spring容器中的ServletWebServerFactory类型的Bean对象,如果没有获取到则抛异常,如果找到多个也抛异常,也就是在Spring容器中只能有一个ServletWebServerFactory类型的Bean对象。
拿到TomcatServletWebServerFactory对象后,就调用它的getWebServer方法,而在这个方法中就会生成一个Tomcat对象,并且利用前面的TomcatConnectorCustomizer等等会Tomcat对象进行配置,最后启动Tomcat。
这样在启动应用时就完成了Tomcat的启动,到此我们通过这个案例也看到了具体的Starter机制、自动配置的具体使用。
不过额外有一点要提一下,我们前面提到了我们可以利用TomcatConnectorCustomizer对Tomca中的Connector组件进行配置,我们可能会想到默认情况下,SpringBoot是不是就是提供了一个TomcatConnectorCustomizer的Bean,然后给Connector配置了8080端口,或者从Environment对象中获取server.port配置,并设置到Connector中去呢?
并不是,因为如果SpringBoot这么实现,那么默认就得提供三个ConnectorCustomizer的Bean,一个TomcatConnectorCustomizer、一个JettyConnectorCustomizer、一个UndertowConnectorCustomizer,这是比较不恰当的,我们知道默认情况下,不管我们是用Tomcat,还是Jetty,还是Undertow,它们启动时绑定的都是8080端口,也就是说SpringBoot并不会根据不同的WebServer设置不同的端口,也就是说SpringBoot只会给WebServer设置端口,而不会区分WebServer的不同实现。
所以在自动配置类ServletWebServerFactoryAutoConfiguration中,会定义一个ServletWebServerFactoryCustomizer类型的Bean,定义为
- @Bean
- public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
- ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
- ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
- return new ServletWebServerFactoryCustomizer(serverProperties,
- webListenerRegistrars.orderedStream().collect(Collectors.toList()),
- cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
- }
这个Bean会接收一个ServerProperties的Bean,ServerProperties的Bean对应的就是properties文件中前缀为server的配置,我们可以利用ServerProperties对象的getPort方法获取到我们所配置的server.port的值。
而ServletWebServerFactoryCustomizer是针对一个ServletWebServerFactory的自定义器,也就是用来配置TomcatServletWebServerFactory这个Bean的,到时候ServletWebServerFactoryCustomizer就会利用ServerProperties对象来对TomcatServletWebServerFactory对象进行设置。
在ServletWebServerFactoryAutoConfiguration这个自动配置上,除开Import了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow这三个配置类,还Import了一个ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,这个BeanPostProcessorsRegistrar会向Spring容器中注册一个WebServerFactoryCustomizerBeanPostProcessor类型的Bean。
WebServerFactoryCustomizerBeanPostProcessor是一个BeanPosrtProcessor,它专门用来处理类型为WebServerFactory的Bean对象,而我们的TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory也都实现了这个接口,所以不管当前项目依赖的情况,只要在Spring在创建比如TomcatServletWebServerFactory这个Bean时,WebServerFactoryCustomizerBeanPostProcessor就会对它进行处理,处理的逻辑为:
比如
这样当TomcatServletWebServerFactory这个Bean对象创建完成后,它里面的很多属性,比如port,就已经是程序员所配置的值了,后续执行getWebServer方法时,就直接获取自己的属性,比如port属性,设置给Tomcat,然后再利用TomcatConnectorCustomizer等会下进行处理,最后启动Tomcat。
到此,SpringBoot整合Tomcat的核心原理就分析完了,主要涉及的东西有
SpringBoot中的底层原理实现中,条件注解时非常重要的,因为条件注解直接决定了一个Bean在什么条件下才会生效,前面已经整理过了SpringBoot中有哪些条件注解,这里就不再重复,本节我们来看看非常核心的两个条件注解@ConditionalOnClass和@ConditionalOnBean的底层是如何实现的。
SpringBoot中众多的条件注解,都是基于Spring中的@Conditional来实现的,所以我们先来用一下@Conditional注解。
先来看下@Conditional注解的定义
- @Target({ElementType.TYPE, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Conditional {
-
- /**
- * All {@link Condition} classes that must {@linkplain Condition#matches match}
- * in order for the component to be registered.
- */
- Class<? extends Condition>[] value();
-
- }
根据定义我们在用@Conditional注解时,需要指定一个或多个Condition的实现类,所以我们先来提供一个实现类
- public class MyCondition implements Condition {
-
- @Override
- public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
- return false;
- }
-
- }
很明显,我们可以在matches方法中来定义条件逻辑
了解这些,我们就来看看@ConditionalOnClass是如何工作的
先来看一个案例
- @Configuration
- @ConditionalOnClass(name = "com.example.Jetty")
- @ConditionalOnMissingClass(value = "com.example.Tomcat")
- public class MyConfiguration {
-
- }
我们在MyConfiguration这个类上使用了两个条件注解:
这两个注解对应的都是@Conditional(OnClassCondition.class),那在OnClassCondition类中是如何对这两个注解进行区分的呢?
Spring在解析到MyConfiguration这个配置时,发现该类上用到了条件注解就会进行条件解析,相关源码如下
- for (Condition condition : conditions) {
- ConfigurationPhase requiredPhase = null;
- if (condition instanceof ConfigurationCondition) {
- requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
- }
-
- // 重点在这
- if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
- return true;
- }
- }
conditions中保存了两个OnClassCondition对象,这段代码会依次调用OnClassCondition对象的matches方法进行条件匹配,一旦某一个条件不匹配就不会进行下一个条件的判断了,这里return的是true,但是这段代码所在的方法叫做shouldSkip,所以true表示忽略。
我们继续看OnClassCondition的matches()方法的实现。
OnClassCondition类继承了FilteringSpringBootCondition,FilteringSpringBootCondition类又继承了SpringBootCondition,而SpringBootCondition实现了Condition接口,matches()方法也是在SpringBootCondition这个类中实现的
- @Override
- public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
- // 获取当前解析的类名或方法名
- String classOrMethodName = getClassOrMethodName(metadata);
- try {
- // 进行具体的条件匹配,ConditionOutcome表示匹配结果
- ConditionOutcome outcome = getMatchOutcome(context, metadata);
-
- // 日志记录匹配结果
- logOutcome(classOrMethodName, outcome);
- recordEvaluation(context, classOrMethodName, outcome);
-
- // 返回true或false
- return outcome.isMatch();
- }
- catch (NoClassDefFoundError ex) {
- // ...
- }
- catch (RuntimeException ex) {
- // ...
- }
- }
所以具体的条件匹配逻辑在getMatchOutcome方法中,而SpringBootCondition类中的getMatchOutcome方法是一个抽象方法,具体的实现逻辑就在子类OnClassCondition中
- @Override
- public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
- ClassLoader classLoader = context.getClassLoader();
- ConditionMessage matchMessage = ConditionMessage.empty();
-
- // 拿到ConditionalOnClass注解中的value值,也就是要判断是否存在的类名
- List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
- if (onClasses != null) {
- // 判断onClasses中不存在的类
- List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
- // 如果有缺失的类,那就表示不匹配
- if (!missing.isEmpty()) {
- return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
- .didNotFind("required class", "required classes").items(Style.QUOTE, missing));
- }
- // 否则就表示匹配
- matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
- .found("required class", "required classes")
- .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
- }
-
- // 和上面类似,只不过是判断onMissingClasses是不是全部缺失,如果是则表示匹配
- List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
- if (onMissingClasses != null) {
- List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
- if (!present.isEmpty()) {
- return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
- .found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
- }
- matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
- .didNotFind("unwanted class", "unwanted classes")
- .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
- }
- return ConditionOutcome.match(matchMessage);
- }
在getMatchOutcome方法中的逻辑为
因为ConditionalOnClass注解和ConditionalOnMissingClass注解的逻辑是比较类似的,所以在源码中都是在OnClassCondition这个类中实现的,假如一个类上即有@ConditionalOnClass,也有@ConditionalOnMissingClass,比如以下代码
- @Configuration
- @ConditionalOnClass(Tomcat.class)
- @ConditionalOnMissingClass(value = "com.example.Tomcat")
- public class MyConfiguration {
-
- }
上面提到的ClassNameFilter.MISSING和ClassNameFilter.PRESENT也比较简单,代码如下
- protected enum ClassNameFilter {
-
- PRESENT {
-
- @Override
- public boolean matches(String className, ClassLoader classLoader) {
- return isPresent(className, classLoader);
- }
-
- },
-
- MISSING {
-
- @Override
- public boolean matches(String className, ClassLoader classLoader) {
- return !isPresent(className, classLoader);
- }
-
- };
-
- abstract boolean matches(String className, ClassLoader classLoader);
-
- static boolean isPresent(String className, ClassLoader classLoader) {
- if (classLoader == null) {
- classLoader = ClassUtils.getDefaultClassLoader();
- }
- try {
- resolve(className, classLoader);
- return true;
- }
- catch (Throwable ex) {
- return false;
- }
- }
-
- }
- protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
- if (classLoader != null) {
- return Class.forName(className, false, classLoader);
- }
- return Class.forName(className);
- }
主要就是用类加载器,来判断类是否存在。
@ConditionalOnBean的底层工作原理
@ConditionalOnBean和@ConditionalOnClass的底层实现应该是差不多的,一个是判断Bean存不存在,一个是判断类存不存在,事实上也确实差不多。
首先@ConditionalOnBean和@ConditionalOnMissingBean对应的都是OnBeanCondition类,OnBeanCondition类也是继承了SpringBootCondition,所以SpringBootCondition类中的getMatchOutcome方法才是匹配逻辑
- @Override
- public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
- ConditionMessage matchMessage = ConditionMessage.empty();
- MergedAnnotations annotations = metadata.getAnnotations();
-
- // 如果存在ConditionalOnBean注解
- if (annotations.isPresent(ConditionalOnBean.class)) {
- Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
- MatchResult matchResult = getMatchingBeans(context, spec);
-
- // 如果某个Bean不存在
- if (!matchResult.isAllMatched()) {
- String reason = createOnBeanNoMatchReason(matchResult);
- return ConditionOutcome.noMatch(spec.message().because(reason));
- }
-
- // 所有Bean都存在
- matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
- matchResult.getNamesOfAllMatches());
- }
-
- // 如果存在ConditionalOnSingleCandidate注解
- if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
- Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
- MatchResult matchResult = getMatchingBeans(context, spec);
-
- // Bean不存在
- if (!matchResult.isAllMatched()) {
- return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
- }
-
- // Bean存在
- Set<String> allBeans = matchResult.getNamesOfAllMatches();
-
- // 如果只有一个
- if (allBeans.size() == 1) {
- matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
- }
- else {
- // 如果有多个
- List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
- spec.getStrategy() == SearchStrategy.ALL);
-
- // 没有主Bean,那就不匹配
- if (primaryBeans.isEmpty()) {
- return ConditionOutcome.noMatch(
- spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
- }
- // 有多个主Bean,那就不匹配
- if (primaryBeans.size() > 1) {
- return ConditionOutcome
- .noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
- }
-
- // 只有一个主Bean
- matchMessage = spec.message(matchMessage)
- .found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
- .items(Style.QUOTE, allBeans);
- }
- }
-
- // 存在ConditionalOnMissingBean注解
- if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
- Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
- ConditionalOnMissingBean.class);
- MatchResult matchResult = getMatchingBeans(context, spec);
-
- //有任意一个Bean存在,那就条件不匹配
- if (matchResult.isAnyMatched()) {
- String reason = createOnMissingBeanNoMatchReason(matchResult);
- return ConditionOutcome.noMatch(spec.message().because(reason));
- }
-
- // 都不存在在,则匹配
- matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
- }
- return ConditionOutcome.match(matchMessage);
- }
逻辑流程为
getMatchingBeans方法中会利用BeanFactory去获取指定类型的Bean,如果没有指定类型的Bean,则会将该类型记录在MatchResult对象的unmatchedTypes集合中,如果有该类型的Bean,则会把该Bean的beanName记录在MatchResult对象的matchedNames集合中,所以MatchResult对象中记录了,哪些类没有对应的Bean,哪些类有对应的Bean。
@ConditionalOnClass和@ConditionalOnBean,这两个条件注解的工作原理就分析到这,总结以下流程就是
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。