SpringBoot自动配置底层核心源码

探究SpringBoot的自动配置原理,我们可以自己写一个启动类的注解。

一、工程创建

首先创建一个工程,工程目录如下:
在这里插入图片描述

  • 自定义一个启动函数:

    package org.springboot;
    
    public class GuoShaoApplication {
        public static void run(Class clazz){
    
        }
    }
    
  • GuoShaoSpringBootApplication 注解:
    自定义一个启动类注解

    package org.springboot;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface GuoShaoSpringBootApplication {
    }
    
    
  • 一个Controller类:
    UserController .class

    package com.guo.user.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
        @GetMapping("/test")
        public String test(){
            return "GuoShao123";
        }
    }
    
    
  • MyApplication.class:
    自定义一个SpringBoot启动类

    package com.guo.user;
    
    import org.springboot.GuoShaoApplication;
    import org.springboot.GuoShaoSpringBootApplication;
    
    @GuoShaoSpringBootApplication
    public class MyApplication {
        public static void main(String[] args){
            GuoShaoApplication.run(MyApplication.class);
        }
    
    }
    

二、进一步改造

接下来思考SpringBoot在启动的时候,应该会做哪些事情呢?


  • run方法跑完后,就能通过8080端口,通过/test接收浏览器的请求。
    那么这里可能会使用到Tomcat,所以涉及到Tomcat的启动
    • 所以首先引入tomcat相关的jar包:

              <dependency>
                  <groupId>javax.servlet</groupId>
                  <artifactId>javax.servlet-api</artifactId>
                  <version>4.0.1</version>
              </dependency>
              <dependency>
                  <groupId>org.apache.tomcat.embed</groupId>
                  <artifactId>tomcat-embed-core</artifactId>
                  <version>9.0.60</version>
              </dependency>
      
    • 然后在run()方法中,添加启动Tomcat的相关代码:

          public static void run(Class clazz){
          startTomcat();
      }
      
      private static void startTomcat() {
          try {
              // 创建 Tomcat 实例
              Tomcat tomcat = new Tomcat();
      
              // 获取 Tomcat 的 Server 对象
              Server server = tomcat.getServer();
      
              // 获取 Service 对象
              Service service = server.findService("Tomcat");
      
              // 创建 Connector,并设置端口
              Connector connector = new Connector();
              connector.setPort(8081);
      
              // 创建 Engine,并设置默认主机
              Engine engine = new StandardEngine();
              engine.setDefaultHost("localhost");
      
              // 创建 Host,并设置名称
              Host host = new StandardHost();
              host.setName("localhost");
      
              // 设置 Context 的路径和监听器
              String contextPath = "";
              Context context = new StandardContext();
              context.setPath(contextPath);
              context.addLifecycleListener(new Tomcat.FixContextListener());
      
              // 将 Context 添加到 Host
              host.addChild(context);
      
              // 将 Host 添加到 Engine
              engine.addChild(host);
      
              // 设置 Service 的容器和连接器
              service.setContainer(engine);
              service.addConnector(connector);
      
              // 启动 Tomcat
              tomcat.start();
              tomcat.getServer().await();
      
          } catch (LifecycleException e) {
              e.printStackTrace();
          }
      }
      
    • 尝试运行我们的SpringBoot启动类,多了一些信息:
      在这里插入图片描述


  • 接下来,浏览器请求/test路径,我们需要能够找到相应的controller指定的方法。
    这个是spring mvc给我们提供的一个功能,所以需要引入spring mvc

    • 引入maven 依赖:

          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.3.18</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aop</artifactId>
              <version>5.3.18</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-web</artifactId>
              <version>5.3.18</version>
          </dependency>
      
    • 在tomcat(tomcat.start();)启动前,添加相关代码:

      DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
      tomcat.addServlet(contextPath, "dispatcher", dispatcherServlet);
      context.addServletMappingDecoded("/*", "dispatcher");
      
      • DispatcherServlet 是什么:

        1. DispatcherServlet 是 Spring MVC 的核心组件,充当前端控制器(Front Controller)。
        2. 它拦截所有进入应用的 HTTP 请求,并将其分发给适当的处理器(如 Controller)。
        3. 这里的appContext是spring的Ioc容器。因为你要找到@Controller方法并处理请求,需要从容器去获取相应的bean。因此需要传入一个Ioc容器
      • 将 DispatcherServlet 添加到 Tomcat:

        1. addServlet 方法:将 DispatcherServlet 以 dispatcher 为名称注册到指定的上下文路径。
        2. contextPath:应用的上下文路径(可以是空字符串,表示根路径 “/”)。
      • addServletMappingDecoded 方法:

        1. 用于定义某个 Servlet 的 URL 映射规则。
          参数:“/*”:URL 路径匹配规则,表示匹配所有请求。
        2. “dispatcher”:前面注册的 Servlet 名称。
        3. 作用:指定所有匹配 /* 的请求都由 “dispatcher”(即 DispatcherServlet)处理。
    • 创建Ioc容器:

      //创建 AnnotationConfigWebApplicationContext 实例
      AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
      applicationContext.register(clazz);
      applicationContext.refresh();
      
      • AnnotationConfigWebApplicationContext:

        1. 它是 Spring 提供的一个专门支持注解配置的应用上下文。
        2. 继承自 GenericWebApplicationContext,支持 Spring 容器中的所有功能,尤其适合基于 Java 配置的应用。
        3. 作用:创建一个新的 Spring 应用上下文实例,供后续注册配置类和管理 Bean。
      • register(Class<?>… annotatedClasses):

        1. 注册一个或多个配置类(通常用 @Configuration 注解标注)。

        2. 配置类是基于 Java 的配置文件,替代传统的 XML 配置文件。

        3. 作用:将指定的配置类加载到 Spring 容器中,用于定义 Bean 和其他容器相关的配置。

        4. 会解析配置类里面@bean,后续会加入到容器中。且会解析配置类头上的注解,拿到注解后还会再进一步解析注解里面有什么东西。
          比如说,SpringBootApplication这个注解就会带有ComponentScan,那么也就会扫描到这个注解并生效
          在这里插入图片描述
          在这里,照猫画虎,在我们的注解里也加上。

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @ComponentScan
          public @interface GuoShaoSpringBootApplication {
          }
          

          如果ComponentScan没有加上扫描路径,Spring会默认的把配置类所在的包路径当作扫描路径
          UserController和配置类在同一个包下面,所以是能够扫描到的。

      • refresh():

        1. 启动或重新刷新应用上下文,触发容器的初始化或刷新过程。
        2. 它会加载所有 Bean 定义,启动单例 Bean 的初始化,初始化资源(如事件、多线程任务等)。
    • 完成以上操作后,tomcat容器中已经添加了拦截器,相关的浏览器请求都会传到DispatcherServlet 中进行处理。然后spring mvc会扫描注配置类解,加载相应的BeanDefinitin,刷新启动spring Ioc容器。然后从Ioc容器获取到UserController bean对象,调用其方法进行处理。

    • 我们尝试启动容器,并发送请求:请求成功
      在这里插入图片描述


目前已经实现了简单的SpringBoot,现在的问题是:如果我们不想要tomcat,想要其他的服务器。

在SpringBoot中,我们只需要修改pom配置文件即可:

        <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>

所以接下来,我们也要实现这个功能。

可以使用策略模式的方式来实现。

首先,添加一个接口类:

package org.springboot;

public interface WebServer {
    public void start();
}

然后实现两个具体的实现类:

package org.springboot;

public class JettyWebServer implements WebServer{
    @Override
    public void start() {
        System.out.println("tomcat start");
    }
}
package org.springboot;

public class TomcatWebServer implements WebServer{
    @Override
    public void start() {
        System.out.println("tomcat start");
    }
}

在这里我们获得的是Ioc容器的bean,所以添加一个函数用于从容器中获取一个服务器bean,并启动:

    public static WebServer getWebServer(WebApplicationContext applicationContext) {
        // 从 ApplicationContext 中获取所有 WebServer 类型的 Bean,返回一个 Map
        Map<String, WebServer> webServers = applicationContext.getBeansOfType(WebServer.class);

        // 如果 Map 为空,抛出 NullPointerException,表明没有找到任何 WebServer 实例
        if (webServers.isEmpty()) {
            throw new NullPointerException("No WebServer beans found in the application context.");
        }

        // 如果 Map 中的实例数量大于 1,抛出 IllegalStateException,表示有多个实例
        if (webServers.size() > 1) {
            throw new IllegalStateException("Multiple WebServer beans found: " + webServers.keySet());
        }

        // 返回唯一的 WebServer 实例
        return webServers.values().stream().findFirst().get();
    }

启动类的run方法:

    public static void run(Class clazz){
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(clazz);
        applicationContext.refresh();
        WebServer webServer = getWebServer(applicationContext);
        webServer.start();
    }

此时,我们可以通过webserver bean的创建来实现服务器的切换。

如,定义了一个bean:

    @Bean
    public WebServer webServerFactory(){
        return new TomcatWebServer();
    }

启动启动类,输出:
在这里插入图片描述
但离springboot还差一些,我们希望能够通过修改pom文件就能实现服务器的切换:

  • 编写配置类:

    package org.springboot;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class WebServerAutoConfiguration {
        @Bean
        @Conditional(TomcatCondition.class)
        public TomcatWebServer tomcatWebServer(){
            return new TomcatWebServer();
        }
    
        @Bean
        @Conditional(JettyCondition.class)
        public JettyWebServer jettyWebServer(){
            return new JettyWebServer();
        }
    }
    

这里使用@Conditional去做一个条件判断,创建相应的服务器bean

  • 接下来要去实现这两个Conditional的逻辑判断。
    如何根据pom是否引入相关依赖,来动态的创建不同的服务器bean呢?
    这里能想到的是使用类加载器去加载指定的类,如果tomcat引入了,那么相关的类就能被加载到;反正就不会被加载到。
    有了以上思路,编写Conditional的逻辑判断

    • TomcatCondition.class
      package org.springboot;
      
      
      import org.springframework.context.annotation.Condition;
      import org.springframework.context.annotation.ConditionContext;
      import org.springframework.core.type.AnnotatedTypeMetadata;
      
      public class TomcatCondition implements Condition {
      
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
              try{
                  context.getClassLoader().loadClass("org.apache.catalina.startup.Tomcat");
      
                  return true;
              }catch (ClassNotFoundException e){
                  return false;
              }
          }
      }
      
    • JettyCondition.class
      package org.springboot;
      
      
      import org.springframework.context.annotation.Condition;
      import org.springframework.context.annotation.ConditionContext;
      import org.springframework.core.type.AnnotatedTypeMetadata;
      
      public class JettyCondition implements Condition {
      
          @Override
          public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
              try{
                  context.getClassLoader().loadClass("org.eclipse.jetty.server.Server");
      
                  return true;
              }catch (ClassNotFoundException e){
                  return false;
              }
          }
      }
      
  • 当前还存在一个问题,这个webservice配置类和spring启动加载时需要的配置类不在同一个包路径下,所以是扫描不到的
    在这里插入图片描述
    所以需要让自定义的springboot能够扫描到它:
    加上一个import注解

    package com.guo.user;
    
    import org.springboot.*;
    import org.springframework.context.annotation.Import;
    
    @GuoShaoSpringBootApplication
    @Import(WebServerAutoConfiguration.class)
    public class MyApplication {
    
        public static void main(String[] args){
            GuoShaoApplication.run(MyApplication.class);
        }
    
    }
    

    当然,为了简化开发,将这个注解加到@GuoShaoSpringBootApplication注解中:

    package org.springboot;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(WebServerAutoConfiguration.class)
    @ComponentScan
    public @interface GuoShaoSpringBootApplication {
    }
    

三、自动配置

原版springboot的SpringBootApplication注解中有一个重要的注解和依赖:
在这里插入图片描述
@EnableAutoConfiguration 是 Spring Boot 中的一个核心注解,用于启用 Spring Boot 的自动配置功能。

它的作用就是,会根据应用程序的依赖(如类路径中的库)和环境(如配置文件)自动配置 Spring 应用程序的基础设施。

以RabbitMQ 为例,当你引入 RabbitMQ 的相关依赖时,Spring Boot 会自动配置与之相关的 Bean。

在这里插入图片描述
我们来看这个引入的jar包中有什么东西
在这里插入图片描述
这里面放了很多相关组件的配置类,如rabbit的配置类。
点击配置类查看:
在这里插入图片描述
这里和我们之前的一样,会通过检测配置类是否存在来判断依赖是否加入。
若存在则会自动配置,创建相应的bean。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@Import({AutoConfigurationImportSelector.class})这是自动配置的核心,通过 AutoConfigurationImportSelector 类动态地选择和导入自动配置的类。

下面看AutoConfigurationImportSelector.class相关代码,

其中核心代码为:

/**
 * 从注解元数据中选择需要导入的配置类。
 * 这是自动配置的核心方法,用于决定哪些配置类会被加载到 Spring 应用上下文中。
 *
 * @param annotationMetadata 当前类的注解元数据(通常是被注解了 @EnableAutoConfiguration 的类)。
 * @return 配置类的全限定类名数组。
 */
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 1. 判断自动配置功能是否被启用
    if (!this.isEnabled(annotationMetadata)) {
        // 如果自动配置被禁用(例如 `spring.boot.enableautoconfiguration=false`),则返回空数组
        return NO_IMPORTS;
    }

    // 2. 获取自动配置条目,包括符合条件的自动配置类列表
    AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = 
        this.getAutoConfigurationEntry(annotationMetadata);

    // 3. 将获取到的配置类列表转换为字符串数组返回
    // `getConfigurations()` 返回经过筛选的配置类名列表
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这个函数会返回符合条件的相关配置类的名字,交给spring进行bean的创建。

继续看 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);

/**
 * 获取自动配置的条目。
 * 包括加载候选的自动配置类、去重、排除、过滤等逻辑。
 *
 * @param annotationMetadata 当前注解元数据,表示被 @EnableAutoConfiguration 注解的类。
 * @return 自动配置条目,包含符合条件的配置类和排除的配置类。
 */
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1. 检查是否启用了自动配置功能
    if (!this.isEnabled(annotationMetadata)) {
        // 如果禁用,则返回一个空的自动配置条目
        return EMPTY_ENTRY;
    } else {
        // 2. 获取 @EnableAutoConfiguration 注解的属性
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

        // 3. 加载所有候选的自动配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

        // 4. 移除重复的配置类,确保列表唯一
        configurations = this.removeDuplicates(configurations);

        // 5. 获取需要排除的配置类(通过 exclude 和 excludeName 属性)
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

        // 6. 检查排除类是否存在于候选配置中(避免配置错误)
        this.checkExcludedClasses(configurations, exclusions);

        // 7. 从候选配置类中移除排除的配置类
        configurations.removeAll(exclusions);

        // 8. 根据条件过滤配置类,例如检查 @ConditionalOnClass、@ConditionalOnMissingBean 等注解
        configurations = this.getConfigurationClassFilter().filter(configurations);

        // 9. 发布事件,用于通知其他组件当前的自动配置加载情况
        this.fireAutoConfigurationImportEvents(configurations, exclusions);

        // 10. 返回最终的自动配置条目,包含符合条件的配置类和排除的配置类
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

核心代码是List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

/**
 * 获取候选自动配置类列表。
 * 通过 SpringFactoriesLoader 从 META-INF/spring.factories 文件中加载配置类的完整类名。
 *
 * @param metadata   当前注解元数据。
 * @param attributes 注解属性(从 @EnableAutoConfiguration 提取)。
 * @return 候选配置类列表。
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 1. 使用 SpringFactoriesLoader 从 spring.factories 文件加载配置类列表。
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        this.getSpringFactoriesLoaderFactoryClass(),  // 获取加载器的工厂类(通常为 EnableAutoConfiguration)
        this.getBeanClassLoader()                    // 获取当前使用的类加载器
    );

    // 2. 确保加载的配置类列表非空
    Assert.notEmpty(
        configurations,
        "No auto configuration classes found in META-INF/spring.factories. " +
        "If you are using a custom packaging, make sure that file is correct."
    );

    // 3. 返回候选配置类的完整类名列表
    return configurations;
}

先讨论一个问题,这么多Jar包,难道要一个一个的去遍历判断是不是配置类吗?

这样复杂度很高,所以spring boot做了优化。在每个jar包中,会有一个文件META-INF/spring.factories :
在这里插入图片描述

这个文件存了很多key-value,其中org.springframework.boot.autoconfigure.EnableAutoConfiguration记录了该jar包下的配置类。

所以通过读取这个文件,就能够避免遍历整个jar包寻找配置类。

接下来继续看代码:

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  1. SpringFactoriesLoader.loadFactoryNames(…):
    SpringFactoriesLoader 是 Spring 框架中的一个工具类,它用于从 META-INF/spring.factories 文件中加载指定的工厂类(即自动配置类)。

  2. this.getSpringFactoriesLoaderFactoryClass():
    这个方法返回一个 Class 类型,它告诉 SpringFactoriesLoader 应该加载哪些自动配置类。
    这里就是传入进来的EnableAutoConfiguration.class,即告诉要去读META-INF/spring.factories中那个key的value。

  3. this.getBeanClassLoader():
    该方法返回当前线程的 ClassLoader,用于加载类。ClassLoader 是用于加载 Java 类和资源文件的对象。Spring 使用它来加载 META-INF/spring.factories 文件。

读取到配置类的路径名字后,需要做一个去冲突操作:

configurations = this.removeDuplicates(configurations);

这个代码比较简单,就是传入到HashSet再转回来:

    protected final <T> List<T> removeDuplicates(List<T> list) {
        return new ArrayList(new LinkedHashSet(list));
    }

接下来到重点,这么多的配置类难道都需要进行返回吗。

答案是否定的,所以需要进行过滤.

有一个想法,去加载相应的配置类,然后去获取Condition这个注解上面的相关class文件,然后判断是否存在即可判断pom中是否引入了相关的依赖。

configurations = this.getConfigurationClassFilter().filter(configurations);

这行代码的作用是对 configurations 列表中加载的类名进行筛选,只保留满足条件的自动配置类。

看过滤过程:

/**
 * 过滤自动配置类列表,根据多个过滤器的规则筛选出有效的类。
 * 
 * @param configurations 原始的自动配置类列表
 * @return 过滤后的有效配置类列表
 */
protected List<String> filter(List<String> configurations) {
    // 记录开始时间,用于统计过滤操作的耗时
    long startTime = System.nanoTime();

    // 将配置类列表转换为数组,便于逐项操作
    String[] candidates = StringUtils.toStringArray(configurations);

    // 标记是否有配置类被过滤掉
    boolean skipped = false;

    // 遍历所有过滤器
    Iterator<AutoConfigurationImportFilter> iterator = this.filters.iterator();
    while (iterator.hasNext()) {
        AutoConfigurationImportFilter filter = iterator.next();
        // 调用当前过滤器的匹配方法,返回每个类是否通过过滤器的布尔结果
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

        // 遍历匹配结果,将未通过过滤的类标记为 null
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                candidates[i] = null; // 将不符合条件的类设置为 null
                skipped = true;       // 设置标记,表示发生了过滤
            }
        }
    }

    // 如果没有类被过滤,直接返回原始列表
    if (!skipped) {
        return configurations;
    }

    // 构建过滤后的列表,跳过所有被设置为 null 的类
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
        if (candidate != null) {
            result.add(candidate); // 将非 null 的类添加到结果列表
        }
    }

    // 如果日志级别为 TRACE,记录过滤掉的类数量和耗时
    if (AutoConfigurationImportSelector.logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size(); // 计算被过滤的类数量
        AutoConfigurationImportSelector.logger.trace(
            "Filtered " + numberFiltered 
            + " auto configuration class in " 
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) 
            + " ms"
        );
    }

    // 返回过滤后的有效配置类列表
    return result;
}

就是逐个使用实现了 AutoConfigurationImportFilter 接口的过滤器进行过滤操作

关键代码:

boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
  • candidates:一个包含候选配置类的数组。

  • this.autoConfigurationMetadata:

    • 自动配置元数据。
    • 包含有关自动配置类的额外信息,例如类的条件、依赖等。
    • 用于辅助过滤器判断某个类是否应该被加载。
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 尝试获取条件评估报告对象,用于记录自动配置类的条件评估结果
    ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);

    // 获取所有候选类的条件评估结果
    ConditionOutcome[] outcomes = this.getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);

    // 初始化匹配结果数组,长度与候选类数组相同
    boolean[] match = new boolean[outcomes.length];

    // 遍历每个候选类的评估结果
    for (int i = 0; i < outcomes.length; ++i) {
        // 如果结果为 null 或匹配条件(isMatch=true),则设置为 true
        match[i] = outcomes[i] == null || outcomes[i].isMatch();

        // 如果当前类不匹配并且评估结果不为 null,则记录相关信息
        if (!match[i] && outcomes[i] != null) {
            // 记录不匹配的评估结果到日志中,方便调试
            this.logOutcome(autoConfigurationClasses[i], outcomes[i]);

            // 如果存在条件评估报告,则将不匹配的评估结果记录到报告中
            if (report != null) {
                report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
            }
        }
    }

    // 返回匹配结果数组
    return match;
}

核心代码是:

ConditionOutcome[] outcomes = this.getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);

继续深入:

protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 如果候选的自动配置类数量大于 1 且 CPU 核心数大于 1,则使用多线程方式处理
    if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
        // 多线程方式解析候选类的条件结果
        return this.resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
    } else {
        // 创建单线程的条件解析器,解析所有候选类的条件结果
        OnClassCondition.OutcomesResolver outcomesResolver = new OnClassCondition.StandardOutcomesResolver(
            autoConfigurationClasses, // 自动配置类名数组
            0,                        // 开始索引
            autoConfigurationClasses.length, // 结束索引
            autoConfigurationMetadata,       // 自动配置元数据
            this.getBeanClassLoader()        // Bean 类加载器
        );
        // 解析并返回条件评估结果
        return outcomesResolver.resolveOutcomes();
    }
}

这里springboot使用了多线程去过滤配置类,提升效率。这里会将配置类分层两份,用两个线程分别去做过滤工作。

下面是多线程处理过程的代码:

private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 将自动配置类列表分为两半
    int split = autoConfigurationClasses.length / 2;

    // 创建解析器处理前半部分
    OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(
        autoConfigurationClasses,  // 自动配置类名数组
        0,                         // 前半部分的起始索引
        split,                     // 前半部分的结束索引
        autoConfigurationMetadata  // 自动配置元数据
    );

    // 创建解析器处理后半部分
    OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(
        autoConfigurationClasses,  // 自动配置类名数组
        split,                     // 后半部分的起始索引
        autoConfigurationClasses.length, // 后半部分的结束索引
        autoConfigurationMetadata, // 自动配置元数据
        this.getBeanClassLoader()  // Bean 类加载器
    );

    // 解析后半部分的条件结果
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();

    // 解析前半部分的条件结果(可以并发执行)
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();

    // 合并前半部分和后半部分的结果到一个结果数组
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length); // 复制前半部分的结果
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length); // 复制后半部分的结果

    // 返回合并后的条件评估结果
    return outcomes;
}

查看核心代码:

    // 解析后半部分的条件结果
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();

深入查看:

public ConditionOutcome[] resolveOutcomes() {
    return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
  • autoConfigurationClasses:
    包含所有需要解析的自动配置类的全限定类名列表。即META-INF/spring.factories 文件。
  • start 和 end:
    决定处理的区间范围:
    • start: 当前线程处理的起始索引。
    • end: 当前线程处理的结束索引。
  • autoConfigurationMetadata
    • 一个优化的数据结构,提供了每个自动配置类的元数据信息(如条件注解的存在性)。
    • 减少了通过反射直接解析类的开销。

第三个参数,又是springboot的一个优化手段。如果要判断过滤结构,需要两步:

  • 加载配置类
  • 解析里面的ConditionalOnClass注解,判断是否满足条件

springboot通过一个文件优化了以上步骤:META-INF\spring-autoconfigure-metadata.properties
在这里插入图片描述
内容如下:
在这里插入图片描述
其实就是将每个配置类的ConditionalOnClass条件都集中记录在这个文件中,这样就可以不加载解析配置类文件,就能进行判断其pom中是否引入相关坐标,是否需要将这个配置类返回给spring实例化

继续深入看代码验证上面所说的:

/**
 * 根据指定范围的自动配置类名和元数据,解析其条件并返回结果数组。
 *
 * @param autoConfigurationClasses 自动配置类的名称数组
 * @param start 起始索引(包含)
 * @param end 结束索引(不包含)
 * @param autoConfigurationMetadata 自动配置的元数据,用于快速查询条件
 * @return 每个类的条件评估结果数组
 */
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
    // 初始化结果数组,大小为指定范围的长度
    ConditionOutcome[] outcomes = new ConditionOutcome[end - start];

    // 遍历指定范围的类名
    for (int i = start; i < end; ++i) {
        String autoConfigurationClass = autoConfigurationClasses[i]; // 当前类名

        // 如果类名不为 null,则继续处理
        if (autoConfigurationClass != null) {
            // 从元数据中获取当前类的 ConditionalOnClass 条件值
            String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");

            // 如果存在条件值,调用 getOutcome 方法评估条件,并保存结果
            if (candidates != null) {
                outcomes[i - start] = this.getOutcome(candidates);
            }
        }
    }

    // 返回评估结果数组
    return outcomes;
}

感兴趣的可以继续往autoConfigurationMetadata.get去深入,可以看到最后是从一个properties文件里去读取相应的key-value。验证了之前所说的。

以上就是spring boot自动配置的原理,对你有帮助,给个关注和点赞吧。【欢迎交流】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值