首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Spring Boot 源码学习】自动装配流程源码解析(下)

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

原创
作者头像
huazie
发布于 2024-05-05 09:14:09
发布于 2024-05-05 09:14:09
3050
举报

引言

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

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

主要内容

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

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

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

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

代码语言:java
AI代码解释
复制
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
  • 添加配置文件属性
  • 我们启动先前建的 Spring Boot 项目的应用类,分别查看到如下的信息:

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

代码语言:java
AI代码解释
复制
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中】,并且已去重过的自动配置组件中不存在该待排除的自动配置类,则认为待排除的自动配置类是非法的,抛出相关异常。

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

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

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

代码语言:java
AI代码解释
复制
configurations.removeAll(exclusions);

2. 过滤自动配置组件

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

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

代码语言:java
AI代码解释
复制
configurations = getConfigurationClassFilter().filter(configurations);

进入 getConfigurationClassFilter 方法,如下所示:

代码语言:java
AI代码解释
复制
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 方法,如下所示:

代码语言:java
AI代码解释
复制
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

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

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

代码语言:java
AI代码解释
复制
# 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 接口。

相关类图如下所示:

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

代码语言:java
AI代码解释
复制
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 的构造函数中,我们可以看到如下:

代码语言:java
AI代码解释
复制
this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);

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

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

代码语言:java
AI代码解释
复制
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 就是通过如下的代码来实现具体的过滤匹配操作。

代码语言:java
AI代码解释
复制
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);

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

代码语言:java
AI代码解释
复制
@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】:

代码语言:java
AI代码解释
复制
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 会再通过一篇博文详细讲解。

3. 触发自动配置事件

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

代码语言:java
AI代码解释
复制
fireAutoConfigurationImportEvents(configurations, exclusions);

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

代码语言:java
AI代码解释
复制
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 的实现类加载出来。

代码语言:java
AI代码解释
复制
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
}

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

代码语言:properties
AI代码解释
复制
# 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 源码学习系列的博文,想要及时了解更新的朋友,关注这里即可

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SpringBoot是如何实现自动配置的?--SpringBoot源码(四)
助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三)
源码笔记
2020/03/07
1.2K0
SpringBoot是如何实现自动配置的?--SpringBoot源码(四)
AutoConfiguration排除指定和过滤自动配置组件
在 上节中我们获得了 spring.factories 文件中注册的自动加载组件,但如果在实际应用的过程中并不需要其中的某个或某些组件,可通过配置@EnableAutoConfiguration 的注解属性 exclude 或 excludeName 进行有针对性的排除,当然也可以通过配置文件进行排除。先通过源代码看看如何获取排除组件的功能。
愿天堂没有BUG
2022/10/28
7310
AutoConfiguration排除指定和过滤自动配置组件
深入Spring Boot自动装配
依赖@Enable模块驱动设计模式,@EnableAutoConfiguration必然会“@Import” ImportSelector或ImportBeanDefinitionRegister的实现类,查看源码:
星哥玩云
2022/09/15
4060
SpringBoot---(20)核心原理:自动化配置1
摘要:本文通过断点追溯源码的方式,来逐步展示SpringBoot的核心功能实现原理:自动化配置;
IT云清
2019/01/22
5950
springboot自动装配原理
使用springboot的过程中我们发现,框架层帮我们自动注册了很多能力,类似的基础配置、集成能力支撑等,我们之前有分析过springboot应用启动的时候自动扫描注册的bean要么是启动门面类路径及子路径下的信息,要么就是用户指定的路径信息,springboot所支持的部件和框架层配置肯定不在我们指定的路径下,按照这个思路应用启动时并不会把相应的配置实例化到上下文中,而我们编写的所有业务代码甚至扩展框架信息都是基于框架能力的支撑,没有这些配置和基础组件是不可能实现的,我们本篇就围绕springboot自动装配展开分析。
叔牙
2022/01/20
1.4K0
springboot自动装配原理
透过Spring自动配置原理看Spring的扩展点
spring的自动配置就是得益于这个EnableAutoConfiguration注解:
tunsuy
2022/10/27
3530
SpringBoot---(21)核心原理:自动化配置2
继续追SpringFactoriesLoader类中的loadFactoryNames这个方法--->
IT云清
2019/01/22
4610
【Spring Boot 源码学习】深入 FilteringSpringBootCondition
前两篇博文笔者带大家从源码深入了解了 Spring Boot 的自动装配流程,其中自动配置过滤的实现由于篇幅限制,还未深入分析。
huazie
2024/05/06
1370
【Spring Boot 源码学习】深入 FilteringSpringBootCondition
这篇带你深入理解SpringBoot中的自动装配(好文精读)
SpringBoot的自动装配是拆箱即用的基础,也是微服务化的前提。其实它并不那么神秘,我在这之前已经写过最基本的实现了,大家可以参考这篇文章。这次主要的议题是,来看看它是怎么样实现的,我们透过源代码来把握自动装配的来龙去脉。
Java团长
2020/04/26
1.2K0
【Spring Boot 源码学习】OnClassCondition 详解
上篇博文带大家从源码深入了自动配置过滤匹配父类 FilteringSpringBootCondition,那么笔者接下来的博文将要介绍它的三个子类 OnClassCondition、OnBeanCondition 和 OnWebApplicationCondition 的实现。
huazie
2024/05/07
1580
【Spring Boot 源码学习】OnClassCondition 详解
Spring Boot 启动注解分析
虽然我们在日常开发中,Spring Boot 使用非常多,算是目前 Java 开发领域一个标配了,但是小伙伴们仔细想想自己的面试经历,和 Spring Boot 相关的面试题都有哪些?个人感觉应该是比较少的,Spring Boot 本质上还是曾经 SSM 那一套,只是通过各种 starter 简化了配置而已,其他都是一模一样的,所以 Spring Boot 中很多面试题还是得回归到 Spring 中去解答!当然这并不是说 Spring Boot 中没什么可问的,Spring Boot 中其实也有一个非常经典的面试题,那就是 Spring Boot 中的自动化配置是怎么实现的?今天松哥就来和各位小伙伴聊一下这个问题。
江南一点雨
2023/09/09
2080
Spring Boot 启动注解分析
SpringBoot 自动配置
看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
ruochen
2021/11/25
1.5K0
SpringBoot - 自动装配 源码解析
主要的功能:进行扫描具有META-INF/spring.factories文件的jar包
小小工匠
2022/04/14
7010
SpringBoot - 自动装配 源码解析
玩转 Spring Boot 原理篇(自动装配源码剖析)(十二)
Spring Boot应用启动时会调用 SpringApplication.run(String... args) 方法。
botkenni
2022/09/23
4880
玩转 Spring Boot 原理篇(自动装配源码剖析)(十二)
SpringBoot:模块探究之spring-boot-autoconfigure
顾名思义,Autoconfigure 就是自动配置的意思,SpringBoot 通过 spring-boot-autoconfigure 体现了 “约定优于配置” 这一设计原则!spring-boot-autoconfigure 也是 SpringBoot 最重要的模块之一!
栗筝i
2022/12/15
3K0
玩转 Spring Boot 原理篇(自动装配源码剖析)
玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)
一猿小讲
2022/04/12
6690
玩转 Spring Boot 原理篇(自动装配源码剖析)
@EnableAutoConfiguration处理逻辑
工作中,我们直接或间接的,用到@EnableAutoConfiguration注解。
温安适
2020/07/10
8470
@EnableAutoConfiguration处理逻辑
SpringBoot(一)自动配置
我以自动配置为SpringBoot的第一篇章,是因为从SpringMvc到SpringBoot,它实现了零配置,并出现了很多默认配置,在进行后面的源码理解时可能会有部分地方不理解,念头不通达,所以先将自动配置这部分给了解清楚,知道它的一个配置是怎么加载的,对后面的学习应该会更流畅一点。
用针戳左手中指指头
2022/11/07
5530
SpringBoot(一)自动配置
Spring Boot 核心特性之组件自动装配
Spring Framework从3.1开始支持@Enable 模块驱动 ,模块是指具备相同领域的功能组件集合组合成为一个独立的单元
itliusir
2018/08/28
8600
Spring Boot 核心特性之组件自动装配
SpringBoot自动装配原理解析
我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序:
Java学习录
2019/09/23
1.4K0
SpringBoot自动装配原理解析
相关推荐
SpringBoot是如何实现自动配置的?--SpringBoot源码(四)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档