SpringBoot自动配置底层核心源码

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自动配置的原理,对你有帮助,给个关注和点赞吧。【欢迎交流】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/932173.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之后端环境搭建

【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目&#xff0c;整合knef4j和mybaits实现基础用户信息管理 后端环境搭建1.1 环境准备1.2 数据库表准备1.3 SpringBoot3项目创建1.4 MySql环境整合&#xff0c;使用druid连接池1.5 整合mybatis-plus1.5.1 引入mybatie…

【书生大模型实战营】Linux 基础知识-L0G1000

前言&#xff1a;书生大模型实战营是上海人工智能实验室开展的大模型系列实践活动&#xff0c;提供免费算力平台&#xff0c;学员通过闯关式任务&#xff0c;可获得免费算力和存储&#xff0c;助力项目实践。本期是第4期&#xff0c;时间从十一月份开始&#xff0c;持续到十二月…

JS进阶DAY3|事件(二)事件流

目录 一、事件流说明 1.1 事件流概念 1.2 事件捕获阶段 1.3 事件冒泡阶段 二、事件传播的两个阶段说明 2.1 事件捕获 2.2 事件冒泡 3.3 示例代码 三、阻止冒泡 四、事件解绑 4.1 removeEventListener方法 4.2 使用 DOM0 级事件属性 4.3 使用一次性事件监听器 一、…

【AI工具】强大的AI编辑器Cursor详细使用教程

目录 一、下载安装与注册 二、内置模型与配置 三、常用快捷键 四、项目开发与问答 五、注意事项与技巧 参考资料 近日&#xff0c;由四名麻省理工学院&#xff08;MIT&#xff09;本科生共同创立的Anysphere公司宣布&#xff0c;其开发的AI代码编辑器Cursor在成立短短两年…

【AWR软件】AWR 软件添加电磁结构

文章目录 前言步骤 前言 微波虚拟 实验 步骤 project -> add em struture -> new em structure 输入名称&#xff0c;create. 添加端口&#xff1a;add edge port

uni-app登录界面样式

非常简洁的登录、注册界面模板&#xff0c;使用uni-app编写&#xff0c;直接复制粘贴即可&#xff0c;无任何引用&#xff0c;全部公开。 废话不多说&#xff0c;代码如下&#xff1a; login.vue文件 <template><view class"screen"><view class"…

普通算法——一维前缀和

一维前缀和 题目链接&#xff1a;https://www.acwing.com/problem/content/797/ 题目描述&#xff1a; 输入一个长度为 n 的整数序列。接下来再输入 m 个询问&#xff0c;每个询问输入一对 l,r。对于每个询问&#xff0c;输出原序列中从第 l 个数到第 r 个数的和。 **什么是…

小程序项目的基本组成结构

分类介绍 项目根目录下的文件及文件夹 pages文件夹 用来存放所有小程序的页面&#xff0c;其中每个页面都由4个基本文件组成&#xff0c;它们分别是&#xff1a; .js文件&#xff1a;页面的脚本文件&#xff0c;用于存放页面的数据、事件处理函数等 .json文件&#xff1a;…

【Go 基础】并发相关

并发相关 CAS CAS算法&#xff08;Compare And Swap&#xff09;&#xff0c;是原⼦操作的⼀种,&#xff0c;CAS 算法是⼀种有名的⽆锁算法。⽆锁编程&#xff0c;即不使⽤锁的情况下实现多线程之间的变量同步。可⽤于在多线程编程中实现不被打断的数据交换操作&#xff0c;从…

【H2O2|全栈】Node.js与MySQL连接

目录 前言 开篇语 准备工作 初始配置 创建连接池 操作数据库 封装方法 结束语 前言 开篇语 本节讲解如何使用Node.js实现与MySQL数据库的连接&#xff0c;并将该过程进行函数封装。 与基础部分的语法相比&#xff0c;ES6的语法进行了一些更加严谨的约束和优化&#…

基于人工智能的新中高考综合解决方案

1. 引言 近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;教育领域也迎来了深刻的变革。针对新中高考改革的需求&#xff0c;本解决方案集成了科大讯飞在人工智能领域的核心技术&#xff0c;旨在通过智能化手段提升教育教学效率与质量&#xff0c;助力学生全面发展。…

【Linux基础】yum 与 vim 的操作

目录 Linux 应用商店——yum yum和yum源是什么 关于镜像的简单理解 yum 的基本操作 yum的安装 yum install 命令 yum查看软件包 yum list 命令 yum的卸载 yum remove 命令 关于 rzsz 软件 安装 rzsz 软件&#xff1a; rz 命令 sz 命令 yum 源拓展 Linux 编辑器…

Elasticsearch数据迁移(快照)

1. 数据条件 一台原始es服务器&#xff08;192.168.xx.xx&#xff09;&#xff0c;数据迁移后的目标服务器&#xff08;10.2.xx.xx&#xff09;。 2台服务器所处环境&#xff1a; centos7操作系统&#xff0c; elasticsearch-7.3.0。 2. 为原始es服务器数据创建快照 修改elas…

【MySQL】数据类型的注意点和应用

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

首次打开韦东山提供的Ubuntu-18.04镜像后,该做哪些事?

目录 01-测试有无网络02-配置最基本的嵌入式开发环境(安装tftp-nfs等)03-缩短关机强制结束进行时间04-关闭软件的自动更新05-未完待续... 01-测试有无网络 ping www.baidu.com 02-配置最基本的嵌入式开发环境(安装tftp-nfs等) 需要安装 tftp&#xff0c;nfs&#xff0c;vim …

2030. gitLab A仓同步到B仓

文章目录 1 A 仓库备份 到 B 仓库2 B 仓库修改main分支的权限 1 A 仓库备份 到 B 仓库 #!/bin/bash# 定义变量 REPO_DIR"/home/xhome/opt/git_sync/zz_xx_xx" # 替换为你的本地库A的实际路径 REMOTE_ORIGIN"http://192.168.1.66:8181/zzkj_software/zz_xx_xx.…

Python与C++混合编程的优化策略与实践

在现代软件开发中&#xff0c;混合编程已成为一种普遍的开发模式。这种模式能够充分发挥不同编程语言的优势&#xff0c;实现性能与开发效率的最佳平衡。本文将深入探讨Python和C混合编程的策略与实践经验。 混合编程就像建造一座现代化的大厦&#xff0c;C就像大厦的钢筋混凝…

【kettle】mysql数据抽取至kafka/消费kafka数据存入mysql

目录 一、mysql数据抽取至kafka1、表输入2、json output3、kafka producer4、启动转换&#xff0c;查看是否可以消费 二、消费kafka数据存入mysql1、Kafka consumer2、Get records from stream3、字段选择4、JSON input5、表输出 一、mysql数据抽取至kafka 1、表输入 点击新建…

INS风格户外风光旅拍人像自拍摄影Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 户外风光旅拍人像自拍摄影结合 Lightroom 调色&#xff0c;可以打造出令人惊艳的视觉效果。这种风格将自然风光与人像完美融合&#xff0c;强调色彩的和谐与氛围感的营造。 预设信息 调色风格&#xff1a;INS风格预设适合类型&#xff1a;人像&#xff0c;户外&…

【burp】burpsuite基础(八)

Burp Suite基础&#xff08;八&#xff09; 声明&#xff1a;该笔记为up主 泷羽的课程笔记&#xff0c;本节链接指路。 警告&#xff1a;本教程仅作学习用途&#xff0c;若有用于非法行为的&#xff0c;概不负责。 ip伪装 安装组件jython 下载好后&#xff0c;在burp中打开扩展…