第六节 自动装配源码理解

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.mzph.cn/diannao/13930.shtml

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

相关文章

Redis数据库知识点

Redis set get del keys redis中有哪些数据类型 string 最大512m key层级 redis的key允许有多个单词形成层级结构&#xff0c;多个单词之间用‘:’隔开 set get del keys hash 本身在redis中存储方式就为key-value, 而hash数据结构中value又是一对key-value hset key …

【easyx】快速入门——弹球小游戏(第一代)

目录 1.需求 2.运动的小球 3.碰到边缘反弹 4.圆周撞击或越过边界反弹 5.绘制和移动挡板 6.小球碰到挡板反弹 7.游戏失败时该如何处理 8.随机初始条件 9.完整代码 我们这一节将结合动画和键盘交互的知识来做一个小游戏 1.需求 我们先看需求:小球在窗体内运动,撞到除…

从入门到精通:详解Linux环境基础开发工具的使用

前言 在这篇文章中&#xff0c;我将深入学习和理解Linux环境基础开发工具的使用。无论你是初学者还是有一定经验的开发者&#xff0c;相信这篇文章都会对你有所帮助。我们将详细讲解软件包管理器、编辑器、编译器、调试器、自动化构建工具以及版本控制工具的使用。 Linux软件…

后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操

之前的SQL语句是基于注解 以后开发中一般是一个接口对应一个映射文件 书写映射文件 基本结构 框架 <?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.or…

尽在掌握:Android 13 通知新功能详解

尽在掌握&#xff1a;Android 13 通知新功能详解 在移动应用开发中&#xff0c;通知扮演着至关重要的角色&#xff0c;它如同应用程序与用户之间的桥梁&#xff0c;及时传递关键信息&#xff0c;提升用户体验。Android 13 作为最新的安卓版本&#xff0c;在通知方面带来了诸多…

Rom应用开发遇到得一些小bug

记录一些细碎得bug ROM时间类问题 问题描述&#xff1a; 设备拔电重启&#xff0c;ROM时间为默认时间如1970年1月1日&#xff0c;与某些业务场景互斥 问题原因&#xff1a; 后台接口校验https证书校验失败&#xff0c;要求是2年内得请求头校验了时间戳&#xff0c;时间戳过期…

QLExpress入门及实战总结

文章目录 1.背景2.简介3.QLExpress实战3.1 基础例子3.2 低代码实战3.2.1 需求描述3.2.1 使用规则引擎3.3.2 运行结果 参考文档 1.背景 最近研究低代码实现后端业务逻辑相关功能&#xff0c;使用LiteFlow作为流程编排后端service服务, 但是LiteFlow官方未提供图形界面编排流程。…

使用RAG和文本转语音功能,我构建了一个 QA 问答机器人

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

代码随想录算法训练营第36期DAY37

DAY37 先二刷昨天的3道题目&#xff0c;每种方法都写&#xff1a;是否已完成&#xff1a;是。 报告&#xff1a;134加油站的朴素法没写对。原因是&#xff1a;在if中缺少了store>0的判断&#xff0c;只给出了indexi的判断。前进法没写出来。因为忘记了总油量的判断。Sum。…

基于springboot图书个性化推荐系统源码数据库

基于springboot图书个性化推荐系统源码数据库 本论文主要论述了如何使用JAVA语言开发一个图书个性化推荐系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述图…

K8s 运维架构师实战课程

阿良课程收益 掌握Kubernetes企业运维管理 掌握部署、运维、存储、网络、监控、日志、CICD、服务网格等实战全面搞定&#xff01; 独立将公司任何项目容器化迁移到K8s平台 生产环境真实案例 大厂企业实战经验 学习最新版、最佳实践 K8s 运维架构师实战【初中级】&#xff1a;ht…

docker 方式gost代理搭建以及代理链实施

一、项目地址&#xff1a;https://github.com/ginuerzh/gost 二、实施 环境信息 主机名公网IP地址内网IP地址角色beijing101.200.xxx.xxx192.168.0.160单层代理serverbeijing101.200.xxx.xxx192.168.0.160链式代理下游serverhk47.238.xxx.xxx172.31.94.207链式代理上游serve…

linux误删crontab定时任务后的补救措施(随手记)

起因 想看一眼定时任务的时候&#xff0c;手误打成了-r&#xff0c;接着我的定时任务就全没了…… 补救措施 我们都知道&#xff0c;crontab的几个关键目录中有一个是/var/log/cron&#xff0c;这个目录记录了crontab执行的日志。 如果平时没有备份crontab的习惯的话&#x…

【MySQL精通之路】InnoDB-内存结构-自适应哈希索引

1.作用 自适应哈希索引使InnoDB能够在具有适当的工作负载组合和足够的缓冲池内存的系统上执行更像内存中的数据库&#xff0c;而不会牺牲事务特性或可靠性。 2.设置 自适应哈希索引由innodb_adaptive_hash_index变量启用 或在服务器启动时由--skip-innodb-adaptive-has…

VMware 安装Windows Server 2008 R2

1.下载镜像 迅雷&#xff1a;ed2k://|file|cn_windows_server_2008_r2_standard_enterprise_datacenter_and_web_with_sp1_x64_dvd_617598.iso|3368839168|D282F613A80C2F45FF23B79212A3CF67|/ 2.安装过程 自定义名字&#xff0c;点击【浏览】选择安装路径 点击【浏览】选择前…

鸿蒙应用开发系列 篇三:ArkTS语言

文章目录 系列文章概述基本语法基本结构概念释疑声明式UI描述高级特性自定义组件页面和自定义组件生命周期状态管理渲染控制ArkTS语言基础类库系列文章 鸿蒙应用开发系列 篇一:鸿蒙系统概述 鸿蒙应用开发系列 篇二:鸿蒙系统开发工具与环境

(Oracle)SQL优化基础(三):看懂执行计划顺序

往期内容&#xff1a; &#xff08;Oracle&#xff09;SQL优化基础&#xff08;一&#xff09;&#xff1a;获取执行计划 &#xff08;Oracle&#xff09;SQL优化基础&#xff08;二&#xff09;&#xff1a;统计信息 获取到执行计划后&#xff0c;对于新手朋友来讲可能不知道…

Qt笔记:动态处理多个按钮点击事件以更新UI

问题描述 在开发Qt应用程序时&#xff0c;经常需要处理多个按钮的点击事件&#xff0c;并根据点击的按钮来更新用户界面&#xff08;UI&#xff09;&#xff0c;如下图。例如&#xff0c;你可能有一个包含多个按钮的界面&#xff0c;每个按钮都与一个文本框和一个复选框相关联…

基于springboot+vue+Mysql的逍遥大药房管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Flutter 中的 FormField 小部件:全面指南

Flutter 中的 FormField 小部件&#xff1a;全面指南 在Flutter的世界里&#xff0c;表单是用户输入数据的基本方式之一。FormField是一个强大的小部件&#xff0c;它将表单字段的创建、验证和管理集成到了一个易于使用的抽象中。本文将为您提供一个全面的指南&#xff0c;帮助…