【Spring Boot 源码学习】自动装配流程源码解析(下)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 本篇介绍自动装配流程后续的内容,包含自动配置组件的排除,过滤 和 相关事件广播

微信图片_20231031101034.png

引言

上篇博文,笔者带大家了解了自动装配流程中有关自动配置加载的流程;

本篇将介绍自动装配流程剩余的内容,包含了自动配置组件的排除和过滤、触发自动配置事件。

主要内容

书接上篇,本篇继续从源码分析自动装配流程:

4. 排除指定自动配置组件

如果我们在实际使用时,并不需要其中的某些组件,那就可以通过 @EnableAutoConfiguration 注解的 excludeexcludeName 属性来进行有针对性的排除 或者 在Spring Boot 的配置文件进行排除。

下面我们来分析一下排除逻辑的源码:

Set<String> exclusions = getExclusions(annotationMetadata, attributes);

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   
   
    Set<String> excluded = new LinkedHashSet<>();
    // 获取 exclude 属性 配置的 待排除的自动配置组件
    excluded.addAll(asList(attributes, "exclude"));
    // 获取 excludeName 属性 配置的 待排除的自动配置组件
    excluded.addAll(asList(attributes, "excludeName"));
    // 获取 Spring Boot 配置文件中 配置的 待排除的自动配置组件
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

protected List<String> getExcludeAutoConfigurationsProperty() {
   
   
    Environment environment = getEnvironment();
    if (environment == null) {
   
   
        return Collections.emptyList();
    }
    if (environment instanceof ConfigurableEnvironment) {
   
   
        Binder binder = Binder.get(environment);
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
            .map(Arrays::asList)
            .orElse(Collections.emptyList());
    }
    String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

上面的代码也挺好理解,分别从注解属性 exclude 、 excludeName 以及配置文件中获取待排除的自动配置组件。

下面我们来演示一下该如何配置,从而排除我们不需要的自动配置组件:

  • 添加注解属性 exclude 和 excludeName
    image.png
  • 添加配置文件属性
    image.png
  • 我们启动先前建的 Spring Boot 项目的应用类,分别查看到如下的信息:
    image.png
    image.png
    image.png

当上面获取了被排除的自动配置组件之后,需要对待排除的类进行检查,如下所示:

checkExcludedClasses(configurations, exclusions);

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
   
   
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    for (String exclusion : exclusions) {
   
   
        // 如果待排除的自动配置类存在且可以加载
        // 并且已去重过的自动配置组件中不存在该待排除的自动配置类
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
   
   
            // 添加到非法的排除列表中
            invalidExcludes.add(exclusion);
        }
    }
    // 如果存在非法的排除项,则抛出相应的异常信息
    if (!invalidExcludes.isEmpty()) {
   
   
        handleInvalidExcludes(invalidExcludes);
    }
}

protected void handleInvalidExcludes(List<String> invalidExcludes) {
   
   
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
   
   
        message.append("\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String.format(
            "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
            message));
}

上述代码中对于待排除类的检查逻辑也好理解,如果待排除的自动配置类存在且可以加载【即存在于当前的ClassLoader中】,并且已去重过的自动配置组件中不存在该待排除的自动配置类,则认为待排除的自动配置类是非法的,抛出相关异常。

我们下面通过示例来验证一下:

  • 在我们的示例项目中添加一个自动配置类【注意这里只做演示,无其他意义】
    image.png
  • 配置文件添加项目中的一个自动配置类
    image.png
  • 我们启动先前建的 Spring Boot 项目的应用类,可以看到如下的启动异常报错:
    image.png

如果上述检查通过,则说明待排除的自动配置类都符合要求,则调用如下代码从自动配置集合中移除上面获取的待排除的自动配置类信息。

configurations.removeAll(exclusions);

5. 过滤自动配置组件

经过上面的自动配置组件排除逻辑之后,接下来就要过滤自动配置组件了,而过滤逻辑主要是通过检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件,来决定该过滤哪些自动配置组件。

下面开始分析相关代码,如下所示【Spring Boot 2.7.9】:

configurations = getConfigurationClassFilter().filter(configurations);

进入 getConfigurationClassFilter 方法,如下所示:

private ConfigurationClassFilter getConfigurationClassFilter() {
   
   
    if (this.configurationClassFilter == null) {
   
   
        List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
        for (AutoConfigurationImportFilter filter : filters) {
   
   
            invokeAwareMethods(filter);
        }
        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    }
    return this.configurationClassFilter;
}

getConfigurationClassFilter 方法返回一个 ConfigurationClassFilter 实例,用来过滤掉不必要的配置类。

继续看 getAutoConfigurationImportFilters 方法,如下所示:

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

它通过 SpringFactoriesLoader 类的 loadFactories 方法来获取 META-INF/spring.factories 中配置 keyAutoConfigurationImportFilterFilters 列表;

我们可以查看相关配置了解一下,如下所示:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

如上所示,在 spring-boot-autoconfigure 中默认配置了三个筛选条件:OnBeanConditionOnClassConditionOnWebApplicationCondition,它们均实现了 AutoConfigurationImportFilter 接口。

相关类图如下所示:

image.png

我们继续往下看 invokeAwareMethods,如下所示:

private void invokeAwareMethods(Object instance) {
   
   
    if (instance instanceof Aware) {
   
   
        if (instance instanceof BeanClassLoaderAware) {
   
   
            ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
        }
        if (instance instanceof BeanFactoryAware) {
   
   
            ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
        }
        if (instance instanceof EnvironmentAware) {
   
   
            ((EnvironmentAware) instance).setEnvironment(this.environment);
        }
        if (instance instanceof ResourceLoaderAware) {
   
   
            ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
        }
    }
}

这里先判断传入的 instance 对象是否是 Aware 接口?

如果是 Aware 接口,则判断是否是它的 BeanClassLoaderAwareBeanFactoryAwareEnvironmentAwareResourceLoaderAware 这 4 个子接口实现?

如果是,则调用对应的回调方法设置相应参数。

Aware 接口是一个一个标记超接口,它表示一个 bean 有资格通过回调方式从 Spring 容器中接收特定框架对象的通知。具体的方法签名由各个子接口确定,但通常应该只包括一个接受单个参数并返回 void 的方法。

继续往下翻看源码,在 getConfigurationClassFilter 方法最后,我们可以看到它返回了一个内部类 ConfigurationClassFilter 的实例对象。

有了内部类 ConfigurationClassFilter ,接下来就可以开始自动配置组件的过滤操作,主要是通过内部类 ConfigurationClassFilterfilter 方法来实现过滤自动配置组件的功能。

不过在分析 filter 方法之前,我们先了解下内部类 ConfigurationClassFilter 中两个成员变量 :

  • List<AutoConfigurationImportFilter> filters : 上面已介绍,它是 META-INF/spring.factories 中配置的 keyAutoConfigurationImportFilterFilters 列表
  • AutoConfigurationMetadata autoConfigurationMetadata :元数据文件 META-INF/ spring-autoconfigure-metadata.properties 中配置对应实体类,详细分析请看下面。

AutoConfigurationMetadata 自动配置元数据,这个前面没有涉及到,从内部类 ConfigurationClassFilter 的构造函数中,我们可以看到如下:

this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);

详细代码,由于篇幅受限,这里就不贴了,大家可以自行查看相关源码,从如下的截图中,我们也可以直观了解下。

image.png
image.png

好了,现在我们进入 filter 方法中,最关键的就是下面 的双层 for 循环处理:

List<String> filter(List<String> configurations) {
   
   
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    // 具体的过滤匹配操作
    for (AutoConfigurationImportFilter filter : this.filters) {
   
   
        boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
   
   
            if (!match[i]) {
   
   
                // 不符合过滤匹配要求,则清空当前的自动配置组件
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // 如果匹配完了,都无需跳过,直接返回当前配置即可
    if (!skipped) {
   
   
        return configurations;
    }
    // 有一个不满足过滤匹配要求,都重新处理并返回符合要求的自动配置组件
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {
   
   
        // 如果当前自动配置组件不满足过滤匹配要求,则上面会被清空
        // 因此这里只需判断即可获取符合要求的自动配置组件
        if (candidate != null) {
   
   
            result.add(candidate);
        }
    }
    if (logger.isTraceEnabled()) {
   
   
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
    }
    return result;
}

翻看上面的 filter 方法源码,我们可以很明显地看到,Spring Boot 就是通过如下的代码来实现具体的过滤匹配操作。

boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

在介绍如何实现具体的过滤匹配操作之前,先来看一下 AutoConfigurationImportFilter 接口的源码:

@FunctionalInterface
public interface AutoConfigurationImportFilter {
   
   
    boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}

上面的 match 方法就是实现具体的过滤匹配操作;

参数:

  • String[] autoConfigurationClasses :待过滤的自动配置类数组
  • AutoConfigurationMetadata autoConfigurationMetadata :自动配置的元数据信息

返回值:

过滤匹配后的结果布尔数组,数组的大小与 autoConfigurationClasses 一致,如果自动配置组件需过滤掉,则设置布尔数组对应值为 false

结合上面的关联类图,我们可以看到 AutoConfigurationImportFilter 接口实际上是由抽象类 FilteringSpringBootCondition 来实现的,另外该抽象类还定义了一个抽象方法 getOutcomes ,然后 OnBeanConditionOnClassConditionOnWebApplicationCondition 继承该抽象类,实现 getOutcomes 方法,完成实际的过滤匹配操作。

抽象类 FilteringSpringBootCondition 的相关源码如下【Spring Boot 2.7.9】:

abstract class FilteringSpringBootCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
   
   

    // 其他代码省略

    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
   
   
        ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
        // 调用 由子类实现的 getOutcomes 方法,完成实际的过滤匹配操作
        ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];
        // 将 getOutcomes 方法返回结果转换成布尔数组
        for (int i = 0; i < outcomes.length; i++) {
   
   
            match[i] = (outcomes[i] == null || outcomes[i].isMatch());
            if (!match[i] && outcomes[i] != null) {
   
   
                logOutcome(autoConfigurationClasses[i], outcomes[i]);
                if (report != null) {
   
   
                    report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
                }
            }
        }
        return match;
    }

    protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata);

    // 其他代码省略
}

通过上面源码可以看出,抽象类 FilteringSpringBootConditionmatch 方法主要是调用 getOutcomes 方法,并将其返回的结果转换成布尔数组。而这个 getOutcomes 方法是过滤匹配的核心功能,由抽象类 FilteringSpringBootCondition 的子类来实现它。

有关 OnBeanConditionOnClassConditionOnWebApplicationCondition 的内容由于篇幅受限,后续 Huazie 会再通过多篇博文详细讲解。

6. 触发自动配置事件

经过上面的排除和过滤之后,我们需要的自动配置类集合已经可以返回了。不过在返回之前,还需要再进行最后一步,触发自动配置导入事件,用来通知所有注册的自动配置监听器进行相关处理。

fireAutoConfigurationImportEvents(configurations, exclusions);

进入 fireAutoConfigurationImportEvents 方法,可以看到如下源码:

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
   
   
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
   
   
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        for (AutoConfigurationImportListener listener : listeners) {
   
   
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

接着,我们进入 getAutoConfigurationImportListeners 方法里,它是通过SpringFactoriesLoader 类提供的 loadFactories 方法将 spring.factories 中配置的接口 AutoConfigurationImportListener 的实现类加载出来。

protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
   
   
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

spring.factories 中配置的自动配置监听器,如下所示:

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

然后,将过滤出的自动配置类集合和被排除的自动配置类集合作为入参创建一个 AutoConfigurationImportEvent 事件对象;

其中 invokeAwareMethods(listener); 类似上面的 invokeAwareMethods(filter); 这里不再赘述了。

最后,调用上述自动配置监听器的 onAutoConfigurationImportEvent 方法,并传入上述获取的 AutoConfigurationImportEvent 事件对象,来通知所有注册的监听器进行相应的处理。

那这样做有什么好处呢?

通过触发 AutoConfigurationImportEvent 事件,来通知所有注册的监听器进行相应的处理,我们就可以在导入自动配置类之后,执行一些附加的自定义逻辑或修改自动配置行为。

总结

本篇 Huazie 带大家通读了 Spring Boot 自动装配逻辑的源码,详细分析了自动装配的后续流程,主要包含 自动配置的排除 和 过滤。超过万字,能够看到这的小伙伴,Huazie 在这感谢各位的支持。后续我将持续输出有关 Spring Boot 源码学习系列的博文,想要及时了解更新的朋友,订阅这里即可

目录
相关文章
|
27天前
|
XML Java 应用服务中间件
SpringBoot项目打war包流程
本文介绍了将Spring Boot项目改造为WAR包并部署到外部Tomcat服务器的步骤。主要内容包括:1) 修改pom.xml中的打包方式为WAR;2) 排除Spring Boot内置的Tomcat依赖;3) 添加Servlet API依赖;4) 改造启动类以支持WAR部署;5) 打包和部署。通过这些步骤,可以轻松地将Spring Boot应用转换为适合外部Tomcat服务器的WAR包。
126 64
SpringBoot项目打war包流程
|
7天前
|
IDE Java 应用服务中间件
spring boot 启动流程
Spring Boot 启动流程简介: 在使用 Spring Boot 之前,启动 Java Web 应用需要配置 Web 容器(如 Tomcat),并将应用打包放入容器目录。而使用 Spring Boot,只需运行 main() 方法即可启动 Web 应用。Spring Boot 的核心启动方法是 SpringApplication.run(),它负责初始化和启动应用上下文。 主要步骤包括: 1. **应用启动计时**:使用 StopWatch 记录启动时间。 2. **打印 Banner**:显示 Spring Boot 的 LOGO。 3. **创建上下文实例**:通过反射创建
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
2月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
2月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
1月前
|
存储 Java 数据安全/隐私保护
SpringBoot整合Flowable【03】- 通过Flowable-UI体验一个简单流程
本文介绍了如何使用Flowable 7.0以下版本的flowable-ui进行流程建模、发布和执行。首先,通过解压并启动flowable-ui war包,访问http://localhost:8080/flowable-ui/idm/#/login登录系统。接着,创建并绘制一个简单的绩效流程模型,包含开始节点、任务节点(自评、上级评、隔级评)和结束节点,并为各节点分配处理人。然后,创建应用并发布绩效流程。最后,通过创建a、b、c三个用户分别完成各节点任务,演示了整个流程的执行过程。本文旨在帮助读者理解Flowable的基本操作和流程元素,后续将介绍通过Java代码控制流程的方法。
119 0
SpringBoot整合Flowable【03】- 通过Flowable-UI体验一个简单流程
|
1月前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
52 0
|
1月前
|
XML 前端开发 Java
SpringBoot整合Flowable【04】- 通过代码控制流程流转
本文介绍了如何使用Flowable的Java API控制流程流转,基于前文构建的绩效流程模型。首先,通过Flowable-UI导出模型文件并部署到Spring Boot项目中。接着,详细讲解了如何通过代码部署、启动和审批流程,涉及`RepositoryService`、`RuntimeService`和`TaskService`等核心服务类的使用。最后,通过实际操作演示了流程从部署到完成的全过程,并简要说明了相关数据库表的变化。本文帮助读者初步掌握Flowable在实际业务中的应用,后续将深入探讨更多高级功能。
88 0
|
27天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
208 17
Spring Boot 两种部署到服务器的方式
|
27天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
61 17
springboot自动配置原理

推荐镜像

更多