当前位置: 首页 > news >正文

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 添加到 Hosthost.addChild(context);// 将 Host 添加到 Engineengine.addChild(host);// 设置 Service 的容器和连接器service.setContainer(engine);service.addConnector(connector);// 启动 Tomcattomcat.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{@Overridepublic void start() {System.out.println("tomcat start");}
}
package org.springboot;public class TomcatWebServer implements WebServer{@Overridepublic void start() {System.out.println("tomcat start");}
}

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

    public static WebServer getWebServer(WebApplicationContext applicationContext) {// 从 ApplicationContext 中获取所有 WebServer 类型的 Bean,返回一个 MapMap<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:

    @Beanpublic 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 {@Overridepublic 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 {@Overridepublic 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);// 遍历匹配结果,将未通过过滤的类标记为 nullfor (int i = 0; i < match.length; i++) {if (!match[i]) {candidates[i] = null; // 将不符合条件的类设置为 nullskipped = 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),则设置为 truematch[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自动配置的原理,对你有帮助,给个关注和点赞吧。【欢迎交流】

相关文章:

SpringBoot自动配置底层核心源码

SpringBoot底层核心源码 一、工程创建二、进一步改造三、自动配置 探究SpringBoot的自动配置原理&#xff0c;我们可以自己写一个启动类的注解。 一、工程创建 首先创建一个工程&#xff0c;工程目录如下&#xff1a; 自定义一个启动函数&#xff1a; package org.springboo…...

故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别(Matlab)

故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别&#xff08;Matlab&#xff09; 目录 故障识别 | GADF-CNN-SSA-XGBoost数据分类预测/故障识别&#xff08;Matlab&#xff09;分类效果基本描述程序设计参考资料 分类效果 基本描述 格拉姆角场差&#xff08;GADF&#…...

【VUE小型网站开发】优化通用配置 二

1. 引入 MyBatis Plus 1.1 添加依赖 <dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- My…...

基于阻塞队列的生产者消费者模型动画演示

一个基于阻塞队列的生产者消费者模型的动画演示&#xff1a; 这是打包好的程序。程序是用 QT 写的。 通过网盘分享的文件&#xff1a;CP模型.7z 链接: https://pan.baidu.com/s/1YjC7YiSqHGqdr6bbffaDWg?pwde6g5 提取码: e6g5 CP模型...

springSecurity认证流程

Spring Security 是spring家族中的一个安全管理框架。相比于另一个安全框架Shiro&#xff0c;它提供更丰富的功能和社区资源&#xff0c;但也较难上手。所以一般大项目用spring Security&#xff0c;小项目用Shiro。 一般web应用需要认证和授权&#xff0c;这也是spring Secur…...

vite5+vue3+Ts5 开源图片预览器上线

images-viewer-vue3&#xff1a;一款Vue3的轻量级图像查看器&#xff0c;它基于Flip动画技术&#xff0c;支持PC和h5移动网页预览照片&#xff0c;如果它是Vue3开发的产品。 npm开源地址:https://www.npmjs.com/package/images-viewer-vue3?activeTabreadme Flip 动画 < …...

Qt开发:元对象系统的介绍和使用

文章目录 概述元对象系统的概念和组成QObject 的详细介绍1.QObject 的主要特性信号与槽机制动态属性对象树&#xff08;Object Trees&#xff09;事件处理 2.QMetaObject的主要特性函数签名常用功能 概述 Qt本身并不是一种编程语言&#xff0c;它实质上是一个跨平台的C开发类库…...

.vscode文件中各个脚本需要修改的地方

NOTE&#xff1a; 此篇文章由VSCode编辑GCC for ARM交叉编译工具链Makefile构建OpenOCD调试&#xff08;基于STM32的标准库&#xff09;派生而来&#xff0c;对.vscode文件中各个脚本需要修改的地方作补充说明。 tasks.json 该json文件的主要作用&#xff1a;使用XX名称去执行…...

JavaScript模块化规范

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…...

第二篇:k8s工作流程

我们来看通过deployment部署pod的常规流程&#xff1a; kubectl向apiserver发送部署请求&#xff08;例如使用 kubectl create -f deployment.yml&#xff09;apiserver将 Deployment 持久化到etcd&#xff1b;etcd与apiserver进行一次http通信。controller manager通过watch a…...

什么是数据架构?

数据架构是如何使用数据的蓝图--它是数据和数据相关资源的高层结构&#xff0c;是整个组织的数据框架&#xff0c;包括模型、规则和标准。通过高效的数据架构&#xff0c;企业可以跟踪整个 IT 基础设施中数据的获取、移动、存储、安全性和可访问性。 数据架构总览 数据架构是…...

ChatGPT、Python和OpenCV支持下的空天地遥感数据识别与计算——从0基础到15个案例实战

在科技飞速发展的时代&#xff0c;遥感数据的精准分析已经成为推动各行业智能决策的关键工具。从无人机监测农田到卫星数据支持气候研究&#xff0c;空天地遥感数据正以前所未有的方式为科研和商业带来深刻变革。然而&#xff0c;对于许多专业人士而言&#xff0c;如何高效地处…...

微信小程序权限授权工具类

最近写微信小程序的时候需要在页面获取设备权限&#xff0c;又不想每个页面都写&#xff0c;就写了一个工具类方便管理 /*** 权限工具类用于获取授权* 权限工具类使用方法&#xff0c;注意调用时函数需要定义为异步函数 async* import PermissionUtils from "./permissio…...

CT中的2D、MPR、VR渲染、高级临床功能

CT中的2D、MPR、VR渲染 在CT&#xff08;计算机断层扫描&#xff09;中&#xff0c;2D、MPR&#xff08;多平面重建&#xff09;、VR&#xff08;体积渲染&#xff09;是不同的图像显示和处理技术&#xff0c;它们各自有独特的用途和优势。下面分别介绍这三种技术&#xff1a;…...

【MySQL】Windows下重启MySQL服务时,报错:服务名无效

1、问题描述 在终端中&#xff0c;停止、启动MySQL服务时报错&#xff1a;服务名无效 2、原因分析 1&#xff09;权限不够 如果是权限不够&#xff0c;会提示&#xff1a;系统错误5&#xff0c;拒绝访问。 2&#xff09;服务名错误 如果是服务名错误&#xff0c;会提示“…...

java枚举的基本用法

在 Java 中&#xff0c;枚举&#xff08;enum&#xff09;是一种特殊的类&#xff0c;用于定义一组常量。它可以使代码更具可读性和可维护性。枚举类型可以用于表示固定的、有限的值集合&#xff0c;比如星期几、季节、方向等。 以下是一些常见的枚举用法示例&#xff1a; 1.…...

Web网络安全

一. 浏览器系统安全方面&#xff0c;使用多进程方案&#xff0c;而js主线程运行在渲染进程中&#xff0c;渲染进程时运行在沙箱中的&#xff0c;没有对本地OS文件的直接读写权限&#xff0c;所以需要通过IPC与浏览器主线程通信&#xff0c;才可以获取cookie等信息&#xff0c;这…...

【分子材料发现】——GAP:催化过程中吸附构型的多模态语言和图学习(数据集处理详解)(二)

Multimodal Language and Graph Learning of Adsorption Configuration in Catalysis https://arxiv.org/abs/2401.07408Paper Data: https://doi.org/10.6084/m9.figshare.27208356.v2 1 Dataset CatBERTa训练的文本字符串输入来源于Open Catalyst 2020 &#xff08;OC20…...

matlab Delaunay三角剖分提取平面点云的边界

目录 一、算法原理1、算法概述2、参考文献二、代码实现三、结果展示四、详细过程版一、算法原理 1、算法概述 Delaunay三角网在生成三角网过程中,以最近的三点形成三角形,且各三角形的边皆不相交,每条边都使用所在的三角形的顶点验算并记录相应的2个顶点坐标。整个验算过程…...

Spring07——AOP通知以及几个相关案例

切入点表达式 注意&#xff0c;不是参数&#xff0c;是参数类型 可以使用通配符描述切入点&#xff0c;快速描述 ■ *&#xff1a;单个独立的任意符号&#xff0c;可以独立出现&#xff0c;也可以作为前缀或者后缀的通配符出现 execution(public∗com.itheima.∗.UserServi…...

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

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

CV工程师专用键盘开源项目硬件分析

1、前言 作为一个电子发烧友&#xff0c;你是否有遇到过这样的问题呢。当我们去查看函数定义的时候&#xff0c;需要敲击鼠标右键之后选择go to definition。更高级一些&#xff0c;我们使用键盘的快捷键来查看定义&#xff0c;这时候可以想象一下&#xff0c;你左手按下ALT&a…...

STM32标准固件库官网下载方法

Keil标准固件库官网下载方法 Keil中DFP.pack下载方法 打开keil官网 Keil 官网 www.keil.com 点击产品 点击“Products” 点击 “Arm Cortex-M” 下拉找到CMSIS-Packs 点击CMSIS-Pack index 搜索对应的MCU&#xff0c;我这里是STM32F4 注意搜索对应系列就好 点击下载...

数据库原理实验实验四 统计查询和组合查询

实验目的和要求加深对统计查询的理解,熟练使用聚簇函数。 实验环境SQL SERVER 2008 SQL Server Management Studio 20 实验内容与过程题目一: 学生(学号,年龄,性别,系名) 课程(课号,课名,学分,学时) 选课(学号,课号,成绩) 根据上面基本表的信息完成下列查…...

【电子通识】案例:USB Type-C USB 3.0线缆做直通连接器TX/RX反向

【电子通识】案例:连接器接线顺序评估为什么新人总是评估不到位?-CSDN博客这个文章的后续。最近在做一个工装项目,需要用到USB Type-C线缆做连接。 此前已经做好了线序规划,结果新人做成实物后发现有的USB Type-C线缆可用,有的不行。其中发现USB3.0的TX-RX信号与自己的板卡…...

【Linux从青铜到王者】数据链路层(mac,arp)以及ip分片

局域网通信 通过之前的学习&#xff0c;我们了解了应用层&#xff0c;传输层&#xff0c;网络层的协议和作用&#xff0c;这里先做个总结 应用层——http&#xff0c;https协议&#xff0c;也可以自己定义一套&#xff0c;作用是进行数据的处理传输层——tcp&#xff0c;udp协…...

MyBatis注解开发

1.配置MyBatis_ssm配置mybits-CSDN博客 2.Mybaits实现增删改查-CSDN博客 3.MyBatis构建动态SQL-CSDN博客 目录 一、注解开发的定义 二、SQL常用注解 三、使用注解完成CRUD 1.查找所有 2.通过id查询 3.增加 4.更新 5.删除 6.查询数量 7.模糊查询 一、注解开发的定义 …...

Unity引擎UI滚动列表——滚动复用扩展应用

大家好&#xff0c;我是阿赵。   之前介绍了滚动复用的基础用法。上一个例子里面&#xff0c;是一个竖向单列的滚动列表展示。这一次来扩展一下用法。如果不知道上一次例子的&#xff0c;可以先往前翻一下&#xff0c;上面有例子的详情、原理解释和代码。 一、 多列的滚动复…...

MySQL 死锁排查

私人博客传送门 MySQL 死锁排查...

AI - 浅聊一下基于LangChain的AI Agent

AI - 浅聊一下基于LangChain的AI Agent 大家好&#xff0c;今天我们来聊聊一个很有意思的主题&#xff1a; AI Agent &#xff0c;就是目前非常流行的所谓的AI智能体。AI的发展日新月异&#xff0c;都2024年末了&#xff0c;如果此时小伙伴们对这个非常火的概念还不清楚的话&a…...

校园综合服务小程序+ssm

摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;校园综合服务被用户普遍使用&#xff0c;为方便用户能够可…...

RPC设计--TcpConnection和TcpServer

TcpConnection 对于服务端来说用来封装clientfd&#xff0c;对于client端来说&#xff0c;就是封装connect函数返回后的fd. 处理此fd上的读写&#xff0c;因此需要为其提供read\write方法&#xff0c;其方法内部调用系统的read\write函数&#xff0c;从fd中读写数据。 当Fd上…...

Mysql 的 B+ 树是否包含行数据?

在 MySQL 中&#xff0c;是否在 B树 的叶子节点上存储完整的行数据&#xff0c;取决于使用的 存储引擎 和 索引类型&#xff1a; 聚簇索引 (Clustered Index) 叶子节点包含完整的行数据。 适用场景&#xff1a;MySQL InnoDB 存储引擎的主键索引&#xff08;或聚簇索引&#xf…...

CSS系列(2)-- 盒模型精解

前端技术探索系列&#xff1a;CSS 盒模型精解 &#x1f4e6; 致读者&#xff1a;深入理解盒模型的本质 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS 盒模型&#xff0c;这是构建网页布局的核心概念。通过本文&#xff0c;你将全面理解盒模型的工作原理及…...

Matlab在信号处理领域有哪些典型的设计实例?

以下是Matlab在信号处理领域的一些典型设计实例&#xff1a; 一、信号生成与频谱分析 正弦信号生成与频谱分析 - 题目&#xff1a;生成一个频率为 f 10 H z f 10Hz f10Hz&#xff0c;采样频率为 f s 100 H z f_s100Hz fs​100Hz&#xff0c;时长为 T 5 s T 5s T5s的正弦…...

frida(objection)中x.ts到x.py封装路径

objection run "android hooking list classes" 基于sensepost/objection.git/1.11.0 sensepost/objection.git/e7eb1 简版路径: android hooking list classes --> show_android_classes --> android_hooking_get_classes androidHookingGetClasses --&g…...

python学习笔记—1—基础环境配置和字面量

1. 字面量 在代码中被写下来的固定值称为字面量 &#xff08;1&#xff09;整数字面量 666 &#xff08;2&#xff09;浮点字面量 6.66 &#xff08;3&#xff09;字符串字面量 "supercarrydoinb" 2. python中的数据类型 3. 打印字面量 print("666") …...

【Windows11系统局域网共享文件数据】

【Windows11系统局域网共享文件数据】 1. 引言1. 规划网络2. 获取必要的硬件3. 设置网络4. 配置网络设备5. 测试网络连接6. 安全性和维护7. 扩展和优化 2. 准备工作2.1: 启用网络发现和文件共享2.2: 设置共享文件夹 3. 访问共享文件夹4. 小贴士5. 总结 1. 引言 随着家庭和小型办…...

包管理器npm, cnpm, yarn 和 pnpm 的命令

npm (Node Package Manager) 安装与更新 npm install 或 npm i&#xff1a; 安装项目依赖&#xff1a;根据 package.json 文件安装所有列出的依赖。参数&#xff1a; -S, --save&#xff1a;保存到 dependencies&#xff08;默认行为&#xff09;。-D, --save-dev&#xff1a;…...

MATLAB 最小二乘平面拟合(90)

MATLAB 最小二乘平面拟合(90) 一、算法介绍二、算法实现1.代码2.结果:一、算法介绍 平面方程: ax+by+cz+d = 0 执行任务:读取一组点云(这里用自定义生成的平面模拟点云代替,在其中添加了噪声来模拟真实的数据),使用最小二乘拟合平面,来输出平面参数,并可视化显示拟…...

kubesphere服务报错 页面无法登陆

kubesphere的页面无法访问 查看pod服务&#xff0c;发现ks-apiserver的pod一直在重启 在所在node节点&#xff0c;执行dmesg -T 发现内存溢出 修改deploy的memory的配置 原本的request memory的值为100M 调整为2G 修改之后&#xff0c;服务正常启动&#xff0c;页面访问正常…...

filezilla连接不上虚拟机的解决方案

现象 解决过程 虚拟机终端输入ip addr 输出&#xff1a; 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever p…...

番茄钟与Todo List:用Go构建高效的时间管理工具

引言 在当今快节奏的世界中&#xff0c;时间管理和任务组织变得越来越重要。为了帮助用户提高效率&#xff0c;我开发了一个基于Golang的开源项目&#xff0c;基于fyne的ui&#xff0c;它结合了经典的番茄工作法&#xff08;Pomodoro Technique&#xff09;和功能丰富的待办事…...

vim实用命令整理(常用的命令)

本章教程,总结自己平时使用vim过程中,经常使用的命令,分享给大家。 一、高频使用 i:进入插入模式(光标处插入) a:进入插入模式(光标后插入) esc:返回普通模式 ::进入命令模式 :w:保存 :q:退出 :wq:保存并退出 :q!:强制退出不保存 :e filename:打开文件 :set n…...

METAGPT

METAGPT: META PROGRAMMING FOR A MULTI-AGENT COLLABORATIVE FRAMEWORK MetaGPT&#xff1a;面向多代理协作框架的元编程 1. 引言 近年来&#xff0c;基于大型语言模型&#xff08;LLMs&#xff09;的多智能体系统在自动问题解决方面取得了显著进展。然而&#xff0c;现有的…...

LabVIEW调用Thorlabs的动态库进行开发

Thorlabs 产品在科研与生产领域中的应用广泛&#xff0c;当需要基于LabVIEW 进行二次开发时&#xff0c;可按照以下方法操作&#xff0c;以充分发挥设备性能并满足特定的项目需求。 创建 Kinesis LabVIEW 项目文件和文件夹 更详细的说明参见附件 在 LabVIEW 的启动界面中选择…...

Lua使用点号和冒号的区别

首先建立一个table&#xff0c;再分别定义两个方法&#xff0c;如下&#xff1a; local meta {}function meta:test1(...)print(self)print("")for k,v in pairs({...}) doprint(v)end endfunction meta.test2(...)print(self)print("")for k,v in pairs…...

Rust学习笔记_13——枚举

Rust学习笔记_10——守卫 Rust学习笔记_11——函数 Rust学习笔记_12——闭包 枚举 文章目录 枚举1. 定义1.1 无值变体1.2 有值变体1.3 枚举与泛型的结合 2. 使用2.1 和匹配模式一起使用2.2 枚举作为类型别名 3. 常用枚举类型 在Rust编程语言中&#xff0c;枚举&#xff08;enum…...

集合框架(1)

集合框架&#xff08;1&#xff09; 1、数组的特点与弊端 &#xff08;1&#xff09;特点&#xff1a; 数组初始化以后&#xff0c;长度就确定了。数组中的添加的元素是依次紧密排列的&#xff0c;有序的&#xff0c;可以重复的。数组声明的类型&#xff0c;就决定了进行元素初…...

【Docker系列】Docker 构建多平台镜像:arm64 架构的实践

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...