AutoConfigurationImportSelector自动导入过程分析

2023-11-07

AutoConfigurationImportSelector

AutoConfigurationImportSelector 类实现 DeferredImportSelector接口,在项目启动过程中,会自动调用其 selectImports 方法,找出需要自动注入的类信息,下面将对其执行过程进行简要分析。

selectImports

该方法比较简单,主要核心逻辑在 getAutoConfigurationEntry 中。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
	// 判断是否需要进行自动注册
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	// 获取自动配置的元数据
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected boolean isEnabled(AnnotationMetadata metadata) {
	// 判断当前类是否是 AutoConfigurationImportSelector
	if (getClass() == AutoConfigurationImportSelector.class) {
		// 判断 spring.boot.enableautoconfiguration 配置是否为 true
		return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
	}
	return true;
}

getAutoConfigurationEntry

该方法主要是获取自动配置的信息,主要包含需要注入的以及需要排除掉的类信息,主要过程如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
	// 判断是否需要进行自动注入
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 获取元数据的参数
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 获取候选需要注入的类
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	// 类去重, 通过 Set 实现
	configurations = removeDuplicates(configurations);
	// 获取需要排除不加载的类
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	// exclusions 类进行检查
	checkExcludedClasses(configurations, exclusions);
	// 从 configurations 中去除掉不需要加载的类
	configurations.removeAll(exclusions);
	// configurations 通过过滤器进行过滤,只留下符合要求的类
	configurations = filter(configurations, autoConfigurationMetadata);
	// 发布自动注入的消息事件,有监听器对其事件进行处理
	fireAutoConfigurationImportEvents(configurations, exclusions);
	// 新建AutoConfigurationEntry对象返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

1、getAttributes

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
	// getAnnotationClass() 返回的内容为 EnableAutoConfiguration.class
	String name = getAnnotationClass().getName();
	// 获取注解里的参数信息
	AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
	Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
				+ " annotated with " + ClassUtils.getShortName(name) + "?");
	return attributes;
}

此方法主要获取参数的信息。
2、getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	// 通过SpringFactoriesLoader加载出需要自动导入的类信息
	// getSpringFactoriesLoaderFactoryClass() 返回的是 EnableAutoConfiguration.class
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    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.");
    return configurations;
}

简要来说,configurations 主要是通过 SpringFactoriesLoader 所加载出来的,具体的原理详见文章~
3、getExclusions

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	Set<String> excluded = new LinkedHashSet<>();
	// 获取 exclude 配置的类
	excluded.addAll(asList(attributes, "exclude"));
	// 获取 excludeName 配置的类
	excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
	// 获取 spring.autoconfigure.exclude 配置的类
	excluded.addAll(getExcludeAutoConfigurationsProperty());
	return excluded;
}

此方法获取不需要进行自动注入的类,通过提取注解里所配置的类以及配置文件里面所配置的类。
4、checkExcludedClasses

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);
        }
    }
    // 如果存在无效类,则抛出异常 IllegalStateException
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}

此方法主要是检查排除的类是否合理,如果存在不满足要求的类,则抛出异常。
5、filter

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    // skip 用来记录是否有需要跳过不加载
    boolean[] skip = new boolean[candidates.length];
    // skipped 用来记录是否有类不需要被加载
    boolean skipped = false;
    // getAutoConfigurationImportFilters()该方法是获取自动注入的过滤器
    // 同样是通过 SpringFactoriesLoader 进行加载
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    	// 对过滤器进行一些初始赋值设置
        invokeAwareMethods(filter);
        // 过滤器对每个候选类进行匹配,match 用来记录匹配结果
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // 如果都需要被加载,则可以直接返回 configurations
    if (!skipped) {
        return configurations;
    }
	// 否则需要将不需要加载的类剔除
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    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 new ArrayList<>(result);
}

此方法主要是通过 SpringFactoriesLoader 对过滤器进行加载,通过过滤器对候选的类进行下一步的过滤。我们知道Bean的注入我们可以添加一些条件,比如说 @Condition 注解 ,这里过滤器就是对每个候选类的注入条件进行检查,将不满足要求的类进行排除。
6、fireAutoConfigurationImportEvents

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
	// 获取监听器,这里同样是通过 SpringFactoriesLoader 进行加载
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
    	// 新建一个 AutoConfigurationImportEvent 事件
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        // 遍历每个监听器,如果能够处理该事件类型,则会对其进行响应
        for (AutoConfigurationImportListener listener : listeners) {
            invokeAwareMethods(listener);
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

此方法主要用到的是监听器,通过发布事件,让对应能够监听该事件类型的监听器对其进行响应,额外增加一些操作。
7、new AutoConfigurationEntry(configurations, exclusions)
最后是新建一个 AutoConfigurationEntry 对象,这里包含需要主动注入的类和需要排除的类,在 selectImports 方法的最后,也是将 configurations 提出,转成数组进行返回,该数组记录的就是经过筛选后的需要导入的类的全限定名。

总结

整个自动导入的过程,回过头来看的话,其实并不是很复杂,但是很值得学习。有些方法看似很简单,就是获取某某某内容,但是去看如何去获取,其实又是一个新的知识点。在学习的过程中,其实有很多不明白的概念,也是希望多查,尽量不要留下不明白的知识点,茅塞顿开的感觉才是最开心的点。以下是我觉得需要进一步学习的内容,本文包含一部分内容的链接,当然源码更需要的是自己去理解,别人的内容都是仅供参考。
1、SpringFactoriesLoader.loadFactoryNames 方法,看一下它是如果找到我们要加载的类信息;
2、了解监听器的概念,发布事件、监听器响应等过程,其中 SpringBoot在启动过程中就有许多发布事件的过程,可以仔细学习;
3、过滤器如果对候选类进行筛选;
4、SpringBoot在启动过程中,何时去调用 selectImports 方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AutoConfigurationImportSelector自动导入过程分析 的相关文章

随机推荐

  • 计算机组成原理(七)——总线BUS

    总线 BUS 总线 BUS 概念 连接计算机系统各个功能部件的信息传输线 是各个部件共享数据及信息的传输介质 用来连接计算机系统各功能部件而构成一个完整系统 实际上是一组信号线 广义地讲 任何连接两个以上电子元器件的导线都可以称为总线 计算
  • 微服务系列文章之 Redisson实现分布式锁

    一 高效分布式锁 当我们在设计分布式锁的时候 我们应该考虑分布式锁至少要满足的一些条件 同时考虑如何高效的设计分布式锁 这里我认为以下几点是必须要考虑的 1 互斥 在分布式高并发的条件下 我们最需要保证 同一时刻只能有一个线程获得锁 这是最
  • 【go】异步任务解决方案Asynq实战

    文章目录 一 Asynq介绍 二 所需工具 三 代码示例 四 Reference 一 Asynq介绍 Asynq 是一个 Go 库 一个高效的分布式任务队列 Asynq 工作原理 客户端 生产者 将任务放入队列 服务器 消费者 从队列中拉出
  • openEuler之RPM软件包管理命令

    RPM命令介绍 安装软件 rpm i xx rpm 卸载软件 rpm e xx rpm 升级形式安装 rpm U xx rpm 常用参数 v 显示详细信息 h 显示文本进度条 1 安装软件 1 下载RPM包 root localhost l
  • K8s企业版多节部署

    K8s企业版多节部署 实验步骤 K8s的单节点部署 master2节点部署 负载均衡部署 使用双机热备 k8s网站页面 实验环境 使用Nginx做负载均衡 lb01 192 168 217 136 24 CentOS 7 5 lb02 19
  • DLL+资源模块切换

    MFC程序中存在一个模块状态 Module State 的问题 也就是资源重复的问题 此处的术语模块是指一个可执行程序 或指其操作不依赖于应用程序的其余部分但使用MFC运行库的共享副本的一个DLL 或一组DLL 我们所创建的MFC DLL就
  • VS2019配置opencv详细图文教程和测试代码

    摘要 vs2019新鲜出炉 配置opencv又有哪些不一样呢 这个教程将会一步一步的教你如何配置opencv和跑动opencv一个简单的项目 测试代码请在原文找到 转发备注原文链接 https xygeng cn post 151 html
  • 机器学习数据获取与处理

    数据获取与处理 以CV任务为主 课程目的 数据的获取途径 数据处理与标注 数据预处理方法 模型训练评估 一 数据集的获取 通常 我们的数据来源于各个比赛平台 首先是AIStudio中的数据集 大部分经典数据集例如百度AI Studio Ka
  • 小程序用户隐私保护协议纯文案修改指引

    目录 一 修改缘由 二 官方指引 三 填写入口 一 修改缘由 小程序提交审核不通过 审核失败原因 存在平台未允许的服务内容 违反 微信小程序平台运营规范常见拒绝情形3 4 详情描述 你好 你的小程序涉及收集 使用和存储用户信息 请增加 用户
  • C++模板的特化(specialization)和偏特化(partial specialization)

    C 模板的特化及偏特化 类模板全特化 对类中的某个成员函数进行特化处理 类模板的偏特化 个数偏特化 范围偏特化 函数模板全特化 函数模板偏特化 模板函数和模板类有的时候可能需要对传入的不同类型进行不同的处理 比如说有的模板传入int或dou
  • 【Linux】进程间通信-命名管道FIFO

    命名管道概述 如果我们要在不相关的进程间交换数据 那么使用FIFO文件将会十分方便 FIFO文件通常也称为命名管道 named pipe 命名管道是一种特殊类型的文件 它在文件系统中以文件名的形式存在 创建命名管道 创建命名管道一般有两种方
  • Unity打包的apk在安卓4.4.2盒子上碰到的问题

    项目场景 Unity开发的项目需要在安卓4 4 2盒子上运行 问题描述 1 会出 从顶部向下滑动即可退出全屏模式 的弹框 这是android4 4的一个特性 叫做沉浸模式 Full screen Immersive Mode 当app启用该
  • 要庆幸,找到了自己-------Day73

    跟朋友聊了大半晚上 看看时间 已经要睡觉的点了 坐下来写这篇文章 只为了感念下曾经的自己 如今的自己 未来的自己 就那么迷茫了那么多年 也坚守了那么多年 如果有方法可以做到 那为何不去努力呢 如果没有人帮 那就自己去克服它 那个守望的孩子就
  • 使用Nginx+Keepalived组建高可用负载平衡Web server集群

    一 首先说明一下网络拓扑结构 1 Nginx 反向代理Server HA Nginx master 192 168 1 157 Nginx backup 192 168 1 158 虚拟IP统一为 192 168 1 110 2 web服务
  • 信号和槽的绑定

    为了更加深入的理解信号和槽的绑定 我们使用以下2种方法来实现绑定 比如我们在QT degisnger界面中添加一个label控件和horizontalScrollBar控件 我们想实现 拖动horizontalScrollBar进度条 la
  • 使用STM32组建基于LoRa的环境监测系统

    文章目录 一 前言 二 介绍 三 硬件连接 1 系统框架 2 中心网关的连接 3 传感器节点1的连接 4 传感器节点2的连接 四 网关程序 1 主程序设计 2 LoRa程序 3 串口1程序 4 LCD显示程序 五 传感器节点程序 1 传感器
  • hexo+git搭建个人博客

    前言 喜欢写 Blog 的人 会经历三个阶段 第一阶段 刚接触 Blog 觉得很新鲜 试着选择一个免费空间来写 第二阶段 发现免费空间限制太多 就自己购买域名和空间 搭建独立博客 第三阶段 觉得独立博客的管理太麻烦 最好在保留控制权的前提下
  • JUC三连问

    1 进程和线程的区别 1 进程是资源分配的基本单位 线程是程序执行的最小单位 2 一个进程包括多个线程 3 每个进程都有自己的内存和资源 一个进程中的线程会共享这些内存和资源 每个线程都有单独的栈内存 和寄存器 2 并行和并发的区别 并行指
  • java 数据结构----------堆栈和队列

    队列的基本概念 队列 简称队 也是一种特殊的线性表 队列的数据元素以及数据元素间的逻辑关系和线性表完全相同 差别是线性表允许在任意位置插入和删除 而队列只允许在一端进行插入操作而在另一端进行删除操作 队列中允许插入操作的一端称为队尾 允许进
  • AutoConfigurationImportSelector自动导入过程分析

    AutoConfigurationImportSelector AutoConfigurationImportSelector 类实现 DeferredImportSelector接口 在项目启动过程中 会自动调用其 selectImpor