Spring Boot的自动配置机制是其核心特性之一,旨在减少开发人员的配置工作。通过自动配置,Spring Boot可以根据项目依赖和环境配置自动地配置Spring应用。

从@SpringBootApplication开始

用于测试的启动类代码:

1@SpringBootApplication
2public class SpringBootApplicationG {
3	public static void main(String[] args) {
4		SpringApplication.run(SpringBootApplicationG.class, args);
5	}
6
7}

SpringBoot启动类的入口注解@SpringBootApplication

 1@Target(ElementType.TYPE)
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Inherited
 5@SpringBootConfiguration
 6@EnableAutoConfiguration
 7@ComponentScan(
 8		excludeFilters = {
 9				@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
10				@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
11		}
12)
13public @interface SpringBootApplication {
14    // ...
15}

@SpringBootApplication注解实际上等价于使用下面三个注解:

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan

所以上面的启动类代码也可以改成下面的样子启动:

 1@SpringBootConfiguration
 2@EnableAutoConfiguration
 3@ComponentScan(
 4		excludeFilters = {
 5				@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 6				@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
 7		}
 8)
 9public class SpringBootApplicationG {
10	public static void main(String[] args) {
11		SpringApplication.run(SpringBootApplicationG.class, args);
12	}
13}

其中起到自动配置类作用的就是@EnableAutoConfiguration,看名字就知道“打开自动配置功能“。但是如果我们去掉@EnableAutoConfiguration注解之后就会抛出下面的异常了,无法启动web server,找不到ServletWebServerFactory这个bean。

1org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
2	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:148) ~[main/:na]


Tomcat自动配置

进入run()方法:

1public ConfigurableApplicationContext run(String... args) {
2    // ...
3    // 刷新Spring容器, 会解析配置类、扫描、启动WebServer
4    refreshContext(context);
5
6    // ...
7}

进入refreshContext(context)方法:

1protected void refresh(ConfigurableApplicationContext applicationContext) {
2    applicationContext.refresh();
3}

该方法内部会调用applicationContext.refresh()方法,直接就触发了Spring容器的刷新机制,而在Spring容器的刷新过程中,留有一个一个扩展口,留给其他容器来扩展的方法:onRefresh();

 1public void refresh() throws BeansException, IllegalStateException {
 2    synchronized (this.startupShutdownMonitor) {
 3        // ...
 4       	
 5        // Initialize other special beans in specific context subclasses.
 6        onRefresh();
 7        
 8        // ...
 9    }
10}

而在SpringBoot中对该方法的实现正视在ServletWebServerApplicationContext中实现的,代码如下:

 1@Override
 2protected void onRefresh() {
 3    super.onRefresh();
 4    try {
 5        // ☆ ->
 6        // 启动tomcat
 7        createWebServer();
 8    } catch (Throwable ex) {
 9        throw new ApplicationContextException("Unable to start web server", ex);
10    }
11}

从上面的代码中可以知道,启动WebServer的入口从这里开始。

ServletWebServerApplicationContext.java

 1private void createWebServer() {
 2		WebServer webServer = this.webServer;
 3		ServletContext servletContext = getServletContext();
 4		if (webServer == null && servletContext == null) {
 5			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
 6			ServletWebServerFactory factory = getWebServerFactory();
 7			createWebServer.tag("factory", factory.getClass().toString());
 8			this.webServer = factory.getWebServer(getSelfInitializer());
 9			createWebServer.end();
10			getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
11			getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
12		} else if (servletContext != null) {
13			try {
14				getSelfInitializer().onStartup(servletContext);
15			} catch (ServletException ex) {
16				throw new ApplicationContextException("Cannot initialize servlet context", ex);
17			}
18		}
19		initPropertySources();
20	}

ServletWebServerFactory

image-20240606144845314

从这个接口的实现关系可以看到,对应的正好是SpringBoot所自带支持的三种web容器:

  1. TomcatServletWebServerFactory
  2. JettyServletWebServerFactory
  3. UndertowServletWebServerFactory

这三个Factory中户创建并启动web容器。比如TomcatServletWebServerFactory

TomcatServletWebServerFactory.java

 1@Override
 2public WebServer getWebServer(ServletContextInitializer... initializers) {
 3    if (this.disableMBeanRegistry) {
 4        Registry.disableRegistry();
 5    }
 6    Tomcat tomcat = new Tomcat();
 7    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
 8    tomcat.setBaseDir(baseDir.getAbsolutePath());
 9    for (LifecycleListener listener : this.serverLifecycleListeners) {
10        tomcat.getServer().addLifecycleListener(listener);
11    }
12    Connector connector = new Connector(this.protocol);
13    connector.setThrowOnFailure(true);
14    tomcat.getService().addConnector(connector);
15    customizeConnector(connector);
16    tomcat.setConnector(connector);
17    tomcat.getHost().setAutoDeploy(false);
18    configureEngine(tomcat.getEngine());
19    for (Connector additionalConnector : this.additionalTomcatConnectors) {
20        tomcat.getService().addConnector(additionalConnector);
21    }
22    prepareContext(tomcat.getHost(), initializers);
23    return getTomcatWebServer(tomcat);
24}
25
26
27protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
28    return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
29}

getWebServer()该方法中对Tomcat的部分参数进行了设置。

getTomcatWebServer()方法中创建了一个TomcatWebServer对象,在该对象的构造方法中会调用initialized()方法,最总会走到this.tomcat.start();方法正式启动Tomcat容器。


如何选择容器

在上面提到的ServletWebServerApplicationContext#createWebServer这个方法中有一个getWebServerFactory()方法会去从Spring容器中获取ServletWebServerFactory自动配置类:

 1protected ServletWebServerFactory getWebServerFactory() {
 2    String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
 3    if (beanNames.length == 0) {
 4        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
 5    }
 6    if (beanNames.length > 1) {
 7        throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
 8    }
 9    return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
10}

根据ServletWebServerFactory这个名字,按规律全局搜索可以找到一个ServletWebServerFactoryAutoConfiguration,这是一个自动配置类:

image-20240606151826061

从这个自动配置类中:

 1@Configuration(proxyBeanMethods = false)
 2@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
 3@ConditionalOnClass(ServletRequest.class)
 4@ConditionalOnWebApplication(type = Type.SERVLET)
 5@EnableConfigurationProperties(ServerProperties.class)
 6@Import({
 7		ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
 8
 9		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
10		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
11		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class
12})
13public class ServletWebServerFactoryAutoConfiguration {
14    // ....
15}
  • @Configuration(proxyBeanMethods = false):声明这是一个配置类,并禁用代理 bean 方法以提高性能。

  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE):设置自动配置的顺序为最高优先级,以确保此配置在其他自动配置之前应用。

  • @ConditionalOnClass(ServletRequest.class):仅在类路径上存在 ServletRequest 类时才启用此配置,确保这是一个 Servlet 环境。

  • @ConditionalOnWebApplication(type = Type.SERVLET):仅在应用程序类型为 Servlet 的 Web 应用时才启用此配置。

  • @EnableConfigurationProperties(ServerProperties.class):启用 ServerProperties 配置属性,以便于通过配置文件(如 application.propertiesapplication.yml)进行配置。

  • @Import:导入其他必要的配置类:

    • ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar:用于注册 BeanPostProcessor

    • ServletWebServerFactoryConfiguration.EmbeddedTomcat:嵌入式 Tomcat 配置。

    • ServletWebServerFactoryConfiguration.EmbeddedJetty:嵌入式 Jetty 配置。

    • ServletWebServerFactoryConfiguration.EmbeddedUndertow:嵌入式 Undertow 配置。

其中ServletWebServerFactoryConfiguration.EmbeddedTomcat和其他两个都是静态内部类,内容如下

 1@Configuration(proxyBeanMethods = false)
 2@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
 3@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
 4static class EmbeddedTomcat {
 5
 6    @Bean
 7    TomcatServletWebServerFactory tomcatServletWebServerFactory(
 8        ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
 9        ObjectProvider<TomcatContextCustomizer> contextCustomizers,
10        ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers
11    ) {
12        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
13        factory.getTomcatConnectorCustomizers().addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
14        factory.getTomcatContextCustomizers().addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
15        factory.getTomcatProtocolHandlerCustomizers().addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
16        return factory;
17    }
18
19}

当容器中存在下面的情况的时候才会想Spring中注册tomcatServletWebServerFactory这个Bean:

  1. 类加载路径中中存在Servlet.classTomcat.class, UpgradeProtocol.class这三个class。
  2. 容器中不存在ServletWebServerFactory

同时在参数中需要依赖于三个参数TomcatConnectorCustomizerTomcatContextCustomizerTomcatProtocolHandlerCustomizer

这些类都是用来自定义Tomcat的,用法如下:

 1@Bean
 2public TomcatConnectorCustomizer tomcatConnectorCustomizer() {
 3    return new TomcatConnectorCustomizer() {
 4        @Override
 5        public void customize(org.apache.catalina.connector.Connector connector) {
 6            connector.setPort(9123);
 7        }
 8    };
 9}
10
11// 启动日志输出:
12// Tomcat started on port(s): 9123 (http) with context path ''

这样可以对Tomcat的一些配置进行自定义,但是我们一般在yaml文件中配置,而不是使用这种方式。

这样配置为什么能起到作用呢,那就需要去看下这些Customizer的后续逻辑了。在上面提到的getWebServer()这个方法中有一个customizeConnector(connector);方法,进入这个方法之后,在这个方法的底部我们可以看到,调用了所有的ConnectCustomizer的customize()方法进行了属性设置,这里会覆盖掉yaml文件中的配置。

 1@Override
 2public WebServer getWebServer(ServletContextInitializer... initializers) {
 3    // ...
 4    customizeConnector(connector);
 5    tomcat.setConnector(connector);
 6    tomcat.getHost().setAutoDeploy(false);
 7    // ...
 8    prepareContext(tomcat.getHost(), initializers);
 9    return getTomcatWebServer(tomcat);
10}

ServletWebServerFactoryAutoConfiguration除了会导入三个和容器类,还会加入一个Registar:就是ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar,那么Spring在启动的时候就会调用该类的registerBeanDefinitions()方法,而它的这个方法又向容器中注册了一个BeanPostProcessorWebServerFactoryCustomizerBeanPostProcessor

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar#registerBeanDefinitions

 1@Override
 2public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
 3                                    BeanDefinitionRegistry registry) {
 4    if (this.beanFactory == null) {
 5        return;
 6    }
 7    registerSyntheticBeanIfMissing(
 8        registry, "webServerFactoryCustomizerBeanPostProcessor",
 9        WebServerFactoryCustomizerBeanPostProcessor.class,
10        WebServerFactoryCustomizerBeanPostProcessor::new
11    );
12
13    registerSyntheticBeanIfMissing(
14        registry,
15        "errorPageRegistrarBeanPostProcessor",
16        ErrorPageRegistrarBeanPostProcessor.class,
17        ErrorPageRegistrarBeanPostProcessor::new
18    );
19}

WebServerFactoryCustomizerBeanPostProcessor被注册成功之后,Spring就会在启动过程中的Bean的初始化之前或者之后来调用它的对应的方法:

 1@Override
 2public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
 3    return bean;
 4}
 5
 6@SuppressWarnings("unchecked")
 7private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
 8    LambdaSafe.callbacks(
 9        WebServerFactoryCustomizer.class,
10        getCustomizers(),
11        webServerFactory
12    )
13        .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
14        .invoke(
15        // ☆ ->
16        // ServletWebServerFactoryCustomizer 读取配置配置文件中server相关的
17        (customizer) -> customizer.customize(webServerFactory)
18    );
19}

从上面的代码中可以看出来,后置方法没有逻辑,而前置方法做的主要内容就是调用ServletWebServerFactoryCustomizer.customize()方法,来完成server相关的配置读取和设置。



条件注解

SpringBoot中为我们提供了很多的条件注解,其中主要有下面的几个:

注解 作用
@ConditionalOnBean 当指定的 bean 存在时,才会加载当前配置。
@ConditionalOnClass 当指定的类存在于类路径时,才会加载当前配置。
@ConditionalOnCloudPlatform 当应用程序运行在特定的云平台时,才会加载当前配置。
@ConditionalOnExpression 根据 SpEL 表达式的结果来决定是否加载当前配置。
@ConditionalOnJava 当运行的 Java 版本满足指定要求时,才会加载当前配置。
@ConditionalOnJndi 当指定的 JNDI 资源存在时,才会加载当前配置。
@ConditionalOnMissingBean 当指定的 bean 不存在时,才会加载当前配置。
@ConditionalOnMissingClass 当指定的类不存在于类路径时,才会加载当前配置。
@ConditionalOnNotWebApplication 当当前应用程序不是 Web 应用时,才会加载当前配置。
@ConditionalOnProperty 当指定的属性有特定值时,才会加载当前配置。
@ConditionalOnResource 当指定的资源存在时,才会加载当前配置。
@ConditionalOnSingleCandidate 当指定的 bean 在上下文中是唯一候选者时,才会加载当前配置。
@ConditionalOnWarDeployment 当应用程序作为 WAR 部署时,才会加载当前配置。
@ConditionalOnWebApplication 当当前应用程序是 Web 应用时,才会加载当前配置。

如果只是简单的额需要一个条件注解,我们可以直接继承SpringBootCondition,实际上SpringBootCondition这个类最后还是实现了Condition接口的,然后重写它的match()方法。

在SpringBoot的条件注解中很多并不是直接继承自SpringBootCondition,而是中间还有其他的一些实现类。判断是否满足条件就是在这个类的matches()方法中判断的。

 1public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
 2    // 针对每个条件注解进行条件判断
 3
 4    // 条件注解写在了哪个类上, 或者哪个方法上
 5    String classOrMethodName = getClassOrMethodName(metadata);
 6    try {
 7        // ☆ ->
 8        // 条件的判断结果
 9        // OnBeanCondition / OnClassCondition
10        ConditionOutcome outcome = getMatchOutcome(context, metadata);
11
12        // 如果log的日志级别为trace, 则记录当前的判断结果
13        logOutcome(classOrMethodName, outcome);
14
15        // 将判断结果记录到ConditionEvaluationReport中
16        // ConditionEvaluationReportLoggingListener 会在收到ContextRefreshedEvent事件后把判断结果用日志的方式打印出来
17        recordEvaluation(context, classOrMethodName, outcome);
18
19        return outcome.isMatch();
20    } catch (NoClassDefFoundError ex) {
21        throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + ex.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", ex);
22    } catch (RuntimeException ex) {
23        throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
24    }
25}

第10行返回的ConditionOutcome中有两个属性:

  • boolean match:表示是否能匹配上
  • ConditionMessage message:记录了如果匹配不上,是缺少那些条件。

代码中的getMatchOutcome(context, metadata)是一个模板方法,交个子类去实现的,比如说下面提到的OnClassCondition中的getMatchOutcome()方法。

logOutcome(classOrMethodName, outcome);方法和 recordEvaluation(context, classOrMethodName, outcome);都是和后面Spring启动的完了之后,见听到ContextRefreshedEvent这个事件的时候,会将前面所有记录的logs都打印出来。对应的监听器为ConditionEvaluationReportLoggingListener

以上面分析过的这个类:ServletWebServerFactoryAutoConfiguration为例子,这个类上有两个条件注解:

  • @ConditionalOnClass(ServletRequest.class)
  • @ConditionalOnWebApplication(type = Type.SERVLET)

ConditionalOnClass

判断某个类是否存在

 1@Target({ ElementType.TYPE, ElementType.METHOD })
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Conditional(OnClassCondition.class)
 5public @interface ConditionalOnClass {
 6
 7	/**
 8	 * The classes that must be present. Since this annotation is parsed by loading class
 9	 * bytecode, it is safe to specify classes here that may ultimately not be on the
10	 * classpath, only if this annotation is directly on the affected component and
11	 * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
12	 * use this annotation as a meta-annotation, only use the {@link #name} attribute.
13	 * @return the classes that must be present
14	 */
15	Class<?>[] value() default {};
16
17	/**
18	 * The classes names that must be present.
19	 * @return the class names that must be present.
20	 */
21	String[] name() default {};
22
23}

可以看到这个这个注解还是依赖了OnClassCondition,下面进入OnClassCondition类中看一下上面提到的getMatchOutcome()方法是怎么实现的。

 1public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
 2    ClassLoader classLoader = context.getClassLoader();
 3    ConditionMessage matchMessage = ConditionMessage.empty();
 4
 5	// 拿到ConditionalOnClass注解中的value值,也就是判断是否存在ConditionalOnClass中配置的条件类名
 6    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
 7    if (onClasses != null) {
 8
 9        // ☆ ->
10        // 判断onClasses中不存在的类
11        List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
12
13        // 如果有缺失的类,那就表示不匹配
14        if (!missing.isEmpty()) {
15            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class", "required classes").items(Style.QUOTE, missing));
16        }
17
18        // 否则就表示匹配
19        matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
20            .found("required class", "required classes")
21            .items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
22    }
23
24    // 如果有@ConditionalOnMissingClass注解则继续解析
25    // 和上面类似,只不过是判断onMissingClasses是不是全部缺失,如果是则表示匹配
26    List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
27    if (onMissingClasses != null) {
28        List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
29        // 判断一下是不是我不想他们存在的那些类都不存在。
30        if (!present.isEmpty()) {
31            return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
32        }
33        matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
34            .didNotFind("unwanted class", "unwanted classes")
35            .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
36    }
37    return ConditionOutcome.match(matchMessage);
38}

第6行到第22行是在找到ConditionnalOnClass中配置的且不存在类路径中的类名,采用的事反向的方法,这样可以方便的在记录下来有哪些类是没有加载到的,并记录到log中。

filter(onClasses, ClassNameFilter.MISSING, classLoader);中使用的是ClassNameFilter.MISSING的匹配逻辑。代码如下:

 1protected final List<String> filter(
 2    Collection<String> classNames,
 3    ClassNameFilter classNameFilter,
 4    ClassLoader classLoader
 5) {
 6    if (CollectionUtils.isEmpty(classNames)) {
 7        return Collections.emptyList();
 8    }
 9
10    List<String> matches = new ArrayList<>(classNames.size());
11    for (String candidate : classNames) {
12        if (classNameFilter.matches(candidate, classLoader)) {
13            matches.add(candidate);
14        }
15    }
16    return matches;
17}
18
19
20// matches()方法对应的实现
21protected enum ClassNameFilter {
22
23    // ...
24
25    MISSING {
26        @Override
27        public boolean matches(String className, ClassLoader classLoader) {
28            return !isPresent(className, classLoader);
29        }
30
31    };
32
33    // ...
34
35    static boolean isPresent(String className, ClassLoader classLoader) {
36        if (classLoader == null) {
37            classLoader = ClassUtils.getDefaultClassLoader();
38        }
39        try {
40            resolve(className, classLoader);
41            return true;
42        } catch (Throwable ex) {
43            return false;
44        }
45    }
46
47}

继续回到getMatchOutcome()方法的26~35行,这里是在看,如果当前类的注解上面除了@ConditionalOnClass还有ConditionalOnMissingClass注解,那么就顺便解析了@ConditionalOnMissingClass。这里比较奇怪,为什么在解析@ConditionalOnClass的注解中还去解析一下@ConditionalOnMissingClass这个注解呢,因为如果按照Spring的解析习惯,两个注解分开来的话,那么SpringBootCondtion.matches()方法就会被执行两次,而且这两个注解的内容比较相似,只是判断条件相反,所以可以顺便解析了。


ConditionalOnBean

 1@Target({ ElementType.TYPE, ElementType.METHOD })
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Conditional(OnBeanCondition.class)
 5public @interface ConditionalOnBean {
 6
 7	Class<?>[] value() default {};
 8
 9	String[] type() default {};
10
11	Class<? extends Annotation>[] annotation() default {};
12
13	String[] name() default {};
14
15	SearchStrategy search() default SearchStrategy.ALL;
16
17	Class<?>[] parameterizedContainer() default {};
18
19}
  • Class<?>[] value() default {}:指定需要存在的 bean 类型,当这些类型的 bean 存在时,加载配置。
  • String[] type() default {}:指定需要存在的 bean 类型名称,以字符串形式表示,当这些类型的 bean 存在时,加载配置。
  • Class<? extends Annotation>[] annotation() default {}:指定需要存在的注解类型,当具有这些注解的 bean 存在时,加载配置。
  • String[] name() default {}:指定需要存在的 bean 名称,当这些名称的 bean 存在时,加载配置。
  • SearchStrategy search() default SearchStrategy.ALL:定义在寻找 bean 时的策略。可以是以下几种:
    • ALL:在当前和所有祖先上下文中查找。
    • CURRENT:仅在当前上下文中查找。
    • ANCESTORS:在当前上下文及其所有祖先上下文中查找。
    • PARENTS:仅在当前上下文的直接父上下文中查找。
  • Class<?>[] parameterizedContainer() default {}:指定需要存在的参数化容器类型(如 List<User>),当这些类型的 bean 存在时,加载配置。

ConditionalOnMissingBean中还有下面两个属性:

  • Class<?>[] ignored() default {};:在匹配的时候需要被忽略掉的bean的类型数组。
  • String[] ignoredType() default {};:在匹配的时候需要忽略掉的bean的名字数组。

类似的进入OnBeanCondition.java看个究竟:

OnBeanCondition.java

 1public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
 2    ConditionMessage matchMessage = ConditionMessage.empty();
 3    MergedAnnotations annotations = metadata.getAnnotations();
 4
 5    // 如果存在ConditionalOnBean注解
 6    if (annotations.isPresent(ConditionalOnBean.class)) {
 7        Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
 8        MatchResult matchResult = getMatchingBeans(context, spec);
 9
10        // 如果某个Bean不存在
11        if (!matchResult.isAllMatched()) {
12            String reason = createOnBeanNoMatchReason(matchResult);
13
14            // 直接返回
15            return ConditionOutcome.noMatch(spec.message().because(reason));
16        }
17
18        // 所有Bean都存在
19        matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE, matchResult.getNamesOfAllMatches());
20    }
21
22    // 如果存在ConditionalOnSingleCandidate注解
23    if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
24        Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
25        MatchResult matchResult = getMatchingBeans(context, spec);
26
27        // 有的bean没有匹配到(不存在) 直接返回
28        if (!matchResult.isAllMatched()) {
29            return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
30        }
31
32        // Bean存在
33        Set<String> allBeans = matchResult.getNamesOfAllMatches();
34
35        // 如果只有一个
36        if (allBeans.size() == 1) {
37            matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
38        } else {
39            // 如果有多个, 看一下bean上面是否有@Primary注解
40            List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans, spec.getStrategy() == SearchStrategy.ALL);
41
42            // 没有主Bean,那就不匹配
43            if (primaryBeans.isEmpty()) {
44                return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
45            }
46
47            // 有多个主Bean,那就不匹配
48            if (primaryBeans.size() > 1) {
49                return ConditionOutcome.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
50            }
51
52            // 只有一个主Bean
53            matchMessage = spec.message(matchMessage)
54                .found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
55                .items(Style.QUOTE, allBeans);
56        }
57    }
58
59    // 存在 ConditionalOnMissingBean 注解
60    if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
61        Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnMissingBean.class);
62        MatchResult matchResult = getMatchingBeans(context, spec);
63
64        // 有任意一个Bean存在,那就条件不匹配
65        if (matchResult.isAnyMatched()) {
66            String reason = createOnMissingBeanNoMatchReason(matchResult);
67            return ConditionOutcome.noMatch(spec.message().because(reason));
68        }
69
70        // 都不存在在,则匹配
71        matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
72    }
73    return ConditionOutcome.match(matchMessage);
74}

从上面的代码中看到和之前的OnClassCondition.java一样,在getMatchOutcome()方法中同事处理了下面三个条件:ConditionalOnBeanConditionalOnSingleCandidateConditionalOnMissingBean

其他的条件注解和上面提到的两个注解逻辑都是差不多的,只是条件不同。



SpringBoot的自动配置

@EnableAutoConfiguration

 1@Target(ElementType.TYPE)
 2@Retention(RetentionPolicy.RUNTIME)
 3@Documented
 4@Inherited
 5@AutoConfigurationPackage
 6@Import(AutoConfigurationImportSelector.class)
 7public @interface EnableAutoConfiguration {
 8
 9	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
10
11	Class<?>[] exclude() default {};
12
13	String[] excludeName() default {};
14
15}
  • Class<?>[] exclude() default {};:根据类型排除某些自动配置类
  • String[] excludeName() default {};:根据名字排除某些自动配置类

导入了一个AutoConfigurationImportSelector.class这个类。会来执行这个类中的selectImports()方法,内容如下:

 1@Override
 2public String[] selectImports(AnnotationMetadata annotationMetadata) {
 3    // 会在所有@Configuration都解析完了之后才执行, 即在解析完程序员所有的配置类后才会来加载
 4    // springboot自己的自动配置类
 5
 6    if (!isEnabled(annotationMetadata)) {
 7        return NO_IMPORTS;
 8    }
 9
10    // ☆ ->
11    // SPI 获取自动配置类(spring.factories中所导入的)
12    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
13
14    // 这里返回的配置类会按照之前的@Condition...的条件一个一个的匹配是否满足
15    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
16}

进入getAutoConfigurationEntry()方法

 1protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
 2    if (!isEnabled(annotationMetadata)) {
 3        return EMPTY_ENTRY;
 4    }
 5
 6    // 获取@EnableAutoConfiguration的属性
 7    AnnotationAttributes attributes = getAttributes(annotationMetadata);
 8
 9    // 获取spring.factories中所有的AutoConfiguration
10    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
11
12    // 去重(也就是按类名去重)
13    configurations = removeDuplicates(configurations);
14
15    // 获取需要排除的AutoConfiguration,可以通过@EnableAutoConfiguration注解的exclude属性,
16    // 或者spring.autoconfigure.exclude来配置
17    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
18
19    // 排除
20    checkExcludedClasses(configurations, exclusions);
21    configurations.removeAll(exclusions);
22
23    // ☆ ->
24    // 获取spring.factories中的AutoConfigurationImportFilter对AutoConfiguration进行过滤
25    // 默认会拿到OnBeanCondition、OnClassCondition、OnWebApplicationCondition
26    // 这三个会去判断上面的AutoConfiguration是否符合它们自身所要求的条件,不符合的会过滤掉,表示不会进行解析了
27    // 会利用spring-autoconfigure-metadata.properties中的配置来进行过滤
28    // spring-autoconfigure-metadata.properties文件中的内容是利用Java中的AbstractProcessor技术在[编译]时生成出来的
29    configurations = getConfigurationClassFilter().filter(configurations);
30
31    // configurations表示合格的,exclusions表示被排除的,
32    // 把它们记录在ConditionEvaluationReportAutoConfigurationImportListener中
33    fireAutoConfigurationImportEvents(configurations, exclusions);
34
35    // 最后返回的AutoConfiguration都是符合条件的
36    return new AutoConfigurationEntry(configurations, exclusions);
37}

getCandidateConfigurations(annotationMetadata, attributes);,这个方法会读取所有的spring.factories内容。

 1	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
 2		Map<String, List<String>> result = cache.get(classLoader);
 3		// ...
 4		try {
 5			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
 6			// ...
 7
 8			// Replace all lists with unmodifiable lists containing unique elements
 9			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
10					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
11			cache.put(classLoader, result);
12		} catch (IOException ex) {
13			// ...
14		}
15		return result;
16	}

先从缓存中拿,如果没有拿到,就会从指定的位置(FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories")读取,最后在存储到缓存中中。

拿到了这些自动配置类了之后还有走前面说过的过滤逻辑,getConfigurationClassFilter().filter(configurations);是通过这一行代码来实现的,这行代码有两个作用,第一个是获取ClassFilter

 1private ConfigurationClassFilter getConfigurationClassFilter() {
 2    if (this.configurationClassFilter == null) {
 3        List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
 4        for (AutoConfigurationImportFilter filter : filters) {
 5            invokeAwareMethods(filter);
 6        }
 7        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
 8    }
 9    return this.configurationClassFilter;
10}
11
12getAutoConfigurationImportFilters();

在通过getAutoConfigurationImportFilters();

1protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
2    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
3}

这个方法会从spring.factories中获取AutoConfigurationImportFilter对应的自动配置类:

1# Auto Configuration Import Filters
2org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
3org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
4org.springframework.boot.autoconfigure.condition.OnClassCondition,\
5org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

获取到这三个条件类之后,就会调用filter()方法:

AutoConfigurationImportSelector.ConfigurationClassFilter

 1List<String> filter(List<String> configurations) {
 2    long startTime = System.nanoTime();
 3    String[] candidates = StringUtils.toStringArray(configurations);
 4    boolean skipped = false;
 5    for (AutoConfigurationImportFilter filter : this.filters) {
 6
 7        // ☆ ->
 8        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
 9        for (int i = 0; i < match.length; i++) {
10            if (!match[i]) {
11                candidates[i] = null;
12                skipped = true;
13            }
14        }
15    }
16    if (!skipped) {
17        return configurations;
18    }
19    List<String> result = new ArrayList<>(candidates.length);
20    for (String candidate : candidates) {
21        if (candidate != null) {
22            result.add(candidate);
23        }
24    }
25    if (logger.isTraceEnabled()) {
26        int numberFiltered = configurations.size() - result.size();
27        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
28                     + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
29    }
30    return result;
31}
32
33}

在第8行,调用match()方法之后进入下面

FilteringSpringBootCondition.java

 1@Override
 2public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
 3    ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
 4
 5    // ☆ ->
 6    // autoConfigurationClasses是所有的那100多个
 7    ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
 8    boolean[] match = new boolean[outcomes.length];
 9    for (int i = 0; i < outcomes.length; i++) {
10        match[i] = (outcomes[i] == null || outcomes[i].isMatch());
11        if (!match[i] && outcomes[i] != null) {
12            logOutcome(autoConfigurationClasses[i], outcomes[i]);
13            if (report != null) {
14                report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
15            }
16        }
17    }
18    return match;
19}

上面getOutcomnes()方法,在OnBeanConditionOnClassConditionOnWebApplicationCondition中有实现,如下图所示。

image-20240607043522423

这里以OnClassCondition为例子来看一下,

 1@Override
 2protected final ConditionOutcome[] getOutcomes(
 3    String[] autoConfigurationClasses,
 4    AutoConfigurationMetadata autoConfigurationMetadata
 5) {
 6    if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
 7        // 如果是多核的会采用多线程去处理
 8        return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
 9    } else {
10        OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
11                                                                         autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
12        return outcomesResolver.resolveOutcomes();
13    }
14}
15
16
17private ConditionOutcome[] resolveOutcomesThreaded(
18    String[] autoConfigurationClasses,
19    AutoConfigurationMetadata autoConfigurationMetadata
20) {
21    int split = autoConfigurationClasses.length / 2;
22    OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
23    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
24    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
25    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
26    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
27    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
28    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
29    return outcomes;
30}

这里为了加快筛选的速度,采用了多线程的去处理的做法,将读取到的自动配置分成了2个数组,主线程调用join的方式等待同步,加快处理速度。

在上面的resolveOutcomes()方法中会对条件进行过滤,一直跟着getOutcome()方法往下走,可以看到如下代码:

1private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
2    if (ClassNameFilter.MISSING.matches(className, classLoader)) {
3        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
4                                        .didNotFind("required class").items(Style.QUOTE, className));
5    }
6    return null;
7}

从这里可以看出来,我们看到现在的代码只是对spring.factories中条件进行初步的过滤,对于我们看到的OnClasssCondtion,它只会保留满足ConditionalOnClass.class这种条件注解的自动配置。

后面才对进行我们最上面分析的其他条件注解的过滤。



— END —