第六节 自动装配源码理解

news2024/11/15 14:04:12

tips:不同版本代码实现有差异。

前面两章了解的流程,就是 SpringBoot 自动转配的核心。

一、自动装配

1.1 什么是 SpringBoot 自动装配?

自动装配是 Spring 框架用来减少配置的显式需求而引入的一个特性,该特性通过 @Autowired或者@Resource注解实现依赖对象的自动注入。而 Spring Boot 在此基础上进一步发展,提出了更高级的自动配置(Auto-configuration)概念。SpringBoot 在启动时会扫描外部 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装配到 Spring 容器里面。

自动装配机制是Spring Boot提供的核心特性之一,它极大地提高了开发效率,同时确保了配置的简洁性和灵活性,实现“约定大于配置”

1.2 自动装配的工作原理

自动装配,抓住三个要素。(在前面章节我们已经分析了其核心流程)

  1. @EnableAutoConfiguration
  2. AutoConfigurationImportSelector
  3. spring.factories

AutoConfigurationImportSelector是自动装配背后的关键角色。实现自ImportSelector接口,它会从 META-INF/spring.factories 文件中加载EnableAutoConfiguration指定的配置类。这是通过SpringFactoriesLoader类来实现的,它为Spring Boot自动装配提供了加载和解析spring.factories文件的能力。

spring.factories: 位于META-INF目录下的spring.factories文件包含了自动配置类的全限定名列表。当应用启动时,这些配置类会被加载并根据条件判断是否应用到当前的应用上下文中。

@EnableAutoConfiguration 作为自动装配的关键。

二、从注解入手

以 mybatis-starter-apply 为例子开始

gitee地址:uzong-starter-learning: 学习 SpringBoot Starter 的工程案例

在我们启动类上有这样的注解 SpringBootApplication,它是开启配置自动装配的大门钥匙。

@SpringBootApplication

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

分析 @SpringBootApplication 注解, 它是多个注解的集成。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

  • @Configuration: SpringBootConfiguration 等于 Configuration。属于派生。
  • @EnableAutoConfiguration: 负责激活 SpringBoot 自动装配机制
  • @ComponentScan: 路径扫描,该扫描路径下的 bean 都会被扫描加载,另外可以排除不需要的类。 即激活 @Conponent 的扫描

@SpringBootApplication 等价于上面三个注解。而实现自动装配的关键注解是 @EnableAutoConfiguration

@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
  • @Import 导入 AutoConfigurationImportSelector 配置类,它会加载各种自动化配置类, 它是实现 Starter 的核心
  • @AutoConfigurationPackag: 由AutoConfigurationPackages.Registrar(一个实现了ImportBeanDefinitionRegistrar的类)处理。默认值是启动类所在的包路径,默认指定启动类路径下的类加载到 Spring 容器。主要逻辑在 AutoConfigurationPackages#register 方法中。 该方法有两个参数 registry 和 packageNames。packageNames 的值默认是启动类包所在的路径。如下所示:

对应的栈帧

registry 即 DefaultListableBeanFactory

Lite && Full (拓展理解)

@Bean 的声明方式为“轻量模式 Lite”

@Configuration 下声明的@Bean为“完全模式Full”,存在 cglib 提升

三、AutoConfigurationImportSelector

关于 AutoConfigurationImportSelector, 它实现了 ImportSelector 接口(3.x 开始)

3.1 ImportSelector

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

ImportSelector 用于条件性的导入配置类。是Spring框架处理@Import注解的一部分,提供了一种灵活的方式来根据运行时的条件动态决定 Spring 配置; 尤其是在开发自定义自动配置和框架扩展时。

selectImports方法的返回值是一个包含配置类全路径名称(Fully qualified names)的字符串数组

ImportSelector可以与注解一起使用,让开发者在@Configuration注解的类上通过@Import来导入它。它不直接将配置类加入到Spring上下文中,而是提供一种选择性地导入配置类的方式。

通过ImportSelector,开发者可以在运行时根据条件(比如classpath中是否存在某个类,或者某个属性是否被定义等)来决定是否导入某些配置。这提供了极大的灵活性,使得 Spring Boot 的自动配置成为可能。

开发一个可插拔的模块或框架时,ImportSelector可以用来根据应用的配置或依赖来动态导入配置类。Spring Boot 大量使用了ImportSelector来实现自动配置。通过条件检查,只有当满足特定条件时,如果类路径中有特定的类存在,或某些属性被定义,相关的配置类才会被导入

3.2 DeferredImportSelector

/**
 * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
 * have been processed. This type of selector can be particularly useful when the selected
 * imports are {@code @Conditional}.
 *
 * <p>Implementations can also extend the {@link org.springframework.core.Ordered}
 * interface or use the {@link org.springframework.core.annotation.Order} annotation to
 * indicate a precedence against other {@link DeferredImportSelector}s.
 *
 * @author Phillip Webb
 * @since 4.0
 */
public interface DeferredImportSelector extends ImportSelector {

}

A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans

have been processed

DeferredImportSelectorImportSelector的一个扩展接口,它在常规的ImportSelector执行之后稍晚运行。这允许其他的配置类先被处理,使得DeferredImportSelector可以在做决定时考虑到这些先前的配置。

而 AutoConfigurationImportSelector 就是实现了 DeferredImportSelector 该接口。

ImportSelector是Spring框架中一个强大的特性,它为条件化配置和自动配置提供了强大的支撑,是实现灵活、动态导入Spring配置的关键机制。通过ImportSelector和它的扩展,Spring实现了一套强大的配置和自动配置机制,极大地简化了Spring应用的配置工作。

3.3 AutoConfigurationImportSelector

核心方法 selectImports

自动配置机制能够有条件地导入合适的配置,这对整合第三方库、开发自定义 starter 或在运行时动态调整配置都是至关重要的

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {

    // 检查自动配置是否启用,如果没有就直接返回一个空的导入数组。
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }

    try {
        // 加载自动配置元数据,可能包括配置类的属性、条件以及其他相关信息。
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);

        // 取得 @EnableAutoConfiguration 注解的属性,这可能含有排除某些配置类的信息。
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 获取所有候选的自动配置类列表。
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);

        // 移除配置类列表中的重复元素。
        configurations = removeDuplicates(configurations);

        // 基于AutoConfigurationMetadata将配置类列表排序。
        configurations = sort(configurations, autoConfigurationMetadata);

        // 获取所有排除的配置类。
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);

        // 校验排除的类是否真正存在于候选配置中,并抛出异常如果有必要。
        checkExcludedClasses(configurations, exclusions);

        // 从候选列表中移除所有排除的类。
        configurations.removeAll(exclusions);

        // 过滤掉那些不应被导入的配置类。
        configurations = filter(configurations, autoConfigurationMetadata);

        // 触发一系列自动配置导入事件。
        fireAutoConfigurationImportEvents(configurations, exclusions);

        // 将配置类列表转换为字符串数组返回。
        return StringUtils.toStringArray(configurations);
    }
   ......
}

3.3 isEnabled

protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
    return getEnvironment().getProperty(
        EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
        true);
}
return true;
}

首先调用 isEnabled 方法去判断自动化配置到底有没有开启,可以通过在 application.properties 中配置 spring.boot.enableautoconfiguration=false 来关闭所有的自动化配置。

3.4 getCandidateConfigurations

获取 claspath\:META-INF/spring.factories 中所有的自动装配类。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    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;
}

注意:getSpringFactoriesLoaderFactoryClass(), 返回的是 EnableAutoConfiguration.class。

到这里,我们应该知道 spring.factories 的前缀,为什么要定义成org.springframework.boot.autoconfigure.EnableAutoConfiguration 这种固定key了吧。

3.5 removeDuplicates

除候选自动化配置类中重复的类。借助 LinkedHashSet 去除重复

protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

3.6 getExclusions

	protected Set<String> getExclusions(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		Set<String> excluded = new LinkedHashSet<>();
		excluded.addAll(asList(attributes, "exclude"));
		excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
		excluded.addAll(getExcludeAutoConfigurationsProperty());
		return excluded;
	}

getExclusions 获取到所有被排除的自动化配置类。

  1. 当前注解的 exclude 属性获取
  2. 当前注解的 excludeName 属性获取
  3. getExcludeAutoConfigurationsProperty() 从配置文件中的 spring.autoconfigure.exclude 属性获取

3.7 checkExcludedClasses

校验不是 not auto-configuration 类则不能使用 exclude 方式做类的排除。

3.8 filter

加载完所有的自动化配置类了,但是,这些配置类是否生效,还需要根据当前项目的依赖等进行加载生效。

比如:MybatisAutoConfiguration 是否能够加载,需要查看当前的依赖是否存在,比如 SqlSessionFactory.class

for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    invokeAwareMethods(filter);
    boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    for (int i = 0; i < match.length; i++) {
        if (!match[i]) {
            skip[i] = true;
            skipped = true;
        }
    }
}

处理下面两个注解

  1. ConditionalOnClass
  2. ConditionalOnMissingClass

3.9 其他方法

fireAutoConfigurationImportEvents:于那些想要在导入配置之前进行特定操作的扩展点。

3.10 小结

到这里,关于 SpringBoot 的Starter 能够进行扩展,来源于 ImportSelector 实现类。 它是能够实现扩展的核心。AutoConfigurationImportSelector 是 SpringBoot 中的类, 但是 ImportSelector 是springframework core 中的类。

这些 Configuration 类,将使用 ConfigurationClassParse 进行解析。

四、 ConfigurationClassParse

processDeferredImportSelectors 导入配置类。

private void processDeferredImportSelectors() {

    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
        ConfigurationClass configClass = deferredImport.getConfigurationClass();
        try {
            String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
            processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
        }
        ......
    }
}

五、本章小结

本章能够实现扩展,是来源于 ImportSelector(DeferredImportSelector) 接口。AutoConfigurationImportSelector(SpringBoot) 整好扩展这个类,使得 Starter 模式能够非常优雅的进行扩展。

那么下一章,我们将进一步扩展理解 ConfigurationClassParse 这部分源码。

已同步发布到公众号:面汤放盐 第六节 自动装配源码理解 (qq.com)

掘金账号:第六节 自动装配源码理解 - 掘金 (juejin.cn)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity Terrain Adjust插件使用教程

一、Terrain Adjust插件介绍 二、插件下载以及导入 1、官方下载地址&#xff1a;Terrain Adjust 2、积分下载地址&#xff1a;Terrain Adjust 下载好之后&#xff0c;回到Unity当中&#xff0c;导入下载好之后的unitypackage包 三、插件使用 1、在使用之前一定要在场景中新…

KuberSphere 安装kubernates

准备机器 最少3台机器 centos Linux 内核&#xff1a;官方建议 3.10 以上 uname -rcontrol 一台 配置 2c 4g worker 二台 配置 2c 4g 3台设备需要用不同的hostname&#xff0c;需要提前修改 hostnamectl set-hostname <新的主机名> # 修改后执行一下命令刷新一下 su -…

【Android】联系人列表补充

真布局--叠起来垂直管 效果展示 部分代码&#xff08;在activity_main&#xff09;里面 <FrameLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

自动驾驶决策规划——坐标转换

以下内容来自b站up主忠厚老实的老王&#xff0c;视频链接&#xff1a;自动驾驶决策规划算法序章 总纲与大致目录_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1hP4y1p7es/?spm_id_from333.999.0.0&vd_sourced36e625f376908cfa88ef5ecf2fb0ed8侵删。 决策规划算法…

解除网页禁止选择

控制台输入以下命令 复制&#xff1a;javascript:void(document.body.οncοpy) 可选&#xff1a;javascript:void(document.body.onselectstart) 拖拉&#xff1a;javascript:void(document.body.οnmοuseup)

SecureFX 9.5.2 SecureCRT 9.5.2 官方下载

SecureCRT是一款由VanDyke Software公司开发的终端仿真软件&#xff0c;它提供了类似于Telnet和SSH等协议的远程访问功能。SecureCRT专门为网络管理员、系统管理员和其他需要保密访问网络设备的用户设计。 SecureCRT具有以下特点&#xff1a; 安全性&#xff1a;SecureCRT支持…

什么是安全左移如何实现安全左移

文章目录 一、传统软件开发面临的安全挑战二、什么是安全左移四、安全左移与安全开发生命周期&#xff08;SDL&#xff09;三、安全左移对开发的挑战五、从DevOps到DevSecOps六、SDL与DevSecOps 一、传统软件开发面临的安全挑战 传统软件开发面临的安全挑战主要包括以下几个方…

Stanford-Coursera 算法Week1 笔记

题外话&#xff1a;全文免费放心食用&#xff0c;作者在此求个 三连关注 1. Integer Multiplication&#xff08;引入&#xff09; &#xff08;很小的时候我们就学过&#xff1a;两个数字相乘的算法——将输入(两个数字)转换为输出(它们的乘积)的一组定义良好的规则&#xf…

MVSnet 代码详解(pytorch)

大致过一下MVSnet 论文中核心的点对应代码应该怎么写。 forward 函数需要 照片&#xff0c;映射矩阵&#xff0c;以及深度值。 照片的shape是 &#xff08;1&#xff0c;5,3&#xff0c;1184,1600&#xff09;代表着1个batch,5张图片&#xff0c;然后一次是每张图片的channel和…

LeetCode547省份数量

题目描述 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市 c 间接相连。省份 是一组直接或间接相连的城市&#xff0c;组内不含其他没有相连的城市。给…

彩色进度条(C语言版本)

.h文件 #include<stdio.h> #include<windows.h>#define NUM 101 #define LOAD_UP 50 #define LOAD_DOWN 60 #define SLEEP_SLOW 300 #define SLEEP_FAST 70 版本1&#xff1a;&#xff08;初始版&#xff09; //v1 #include "progress.h" int main() …

使用 Sonatype Nexus Repository Manager 如何安装npm.md

1. 安装与启动 Nexus2. 登录 Nexus Web UI3. 创建 npm 仓库4. &#xff08;可选&#xff09;配置 npm 代理仓库5. 创建 npm 仓库组6. 配置 npm 客户端7. 测试和使用 Sonatype Nexus Repository Manager (通常简称 Nexus) 是一个强大的二进制管理系统&#xff0c;用于存储和管理…

北京大学肖臻老师《区块链技术与应用》P20(挖矿难度调整)和P21(权益证明)

1️⃣ 参考 北京大学肖臻老师《区块链技术与应用》 P20 - ETH挖矿难度调整篇P21 - ETH权益证明篇 2️⃣0️⃣ ETH挖矿难度调整 在ETH中最长合法链也是最难合法链&#xff08;Total Difficulty&#xff09; 比特币与以太坊难度的宏观比较 比特币以太坊调整难度隔2016个区块…

LiveGBS流媒体平台GB/T28181用户手册-版本信息:查看机器码、切换查看流媒体服务

LiveGBS流媒体平台GB/T28181用户手册--版本信息:查看机器码、切换查看流媒体服务 1、版本信息1.1、查看机器码1.2、多个流媒体服务1.3、提交激活 2、搭建GB28181视频直播平台 1、版本信息 版本信息页面&#xff0c;可以查看到信令服务 流媒体服务相关信息&#xff0c;包含硬件…

MySQL5个查询

# 总查询 EXPLAIN SELECT * FROM city; # 范围查询 EXPLAIN SELECT * from city where ID>5 and ID<20; #主键查询 EXPLAIN SELECT * from city where ID5; # 索引查询 EXPLAIN SELECT * from city where CountryCodeNLD; # 普通索引 EXPLAIn SELECT * from cit…

鸿蒙OS开发:【一次开发,多端部署】(多设备自适应能力)实例

多设备自适应能力 介绍 此Demo展示在JS中的多设备自适应能力&#xff0c;包括资源限定词、原子布局和响应式布局。 效果预览 使用说明 1.本示例中的资源限定词和响应式布局针对常见设备类型做了适配&#xff0c;可以在预览器中开启"Multi-profile preview"进行多…

创建桌面快捷方式

①点击桌面任务栏中的【开始图标】>点击【所有应用】 ②将【EndNote】图标拖到电脑桌面。

用kimi一键绘制《庆余年》人物关系图谱

《庆余年》里面人物关系复杂&#xff0c;如果能画出一个人物关系图谱&#xff0c;可以直观的理解其中人物关系&#xff0c;更好的追剧。 首先&#xff0c;用kimi下载庆余年的分集剧情&#xff0c;常见文章《AI网络爬虫&#xff1a;批量爬取电视猫上面的《庆余年》分集剧情》&am…

【WebGIS实例】(15)GeoServer 备份地图服务器

笔者GeoServer版本号&#xff1a;2.23.4 官方教程文档&#xff1a;Backup and Restore Documentation — GeoServer 2.26.x User Manual 一、下载社区插件 选择对应的版本&#xff1a;Index of /geoserver/进入插件列表&#xff1a;Index of /geoserver/2.23.x/community-late…

无人机监测系统:天空之眼,精准掌握地球脉动

在当今信息化快速发展的时代&#xff0c;无人机技术以其独特的优势&#xff0c;正在成为资源调查、环境监测和规划支持的重要工具。无人机监测系统通过搭载多种传感器和设备&#xff0c;能够快速、高效地获取地表信息&#xff0c;为决策提供科学依据。 项目背景 随着全球环境…