当前位置: 首页 > news >正文

【Spring】深究SpringBoot自动装配原理

文章目录

    • 前言
    • 1、main入口
    • 2、@SpringBootApplication
    • 3、@EnableAutoConfiguration
    • 4、AutoConfigurationImportSelector
      • 4.1、selectImports()
      • 4.2、getAutoConfigurationEntry()
      • 4.3、getCandidateConfigurations()
      • 4.4、loadFactoryNames()
    • 5、META-INF/spring.factories
    • 6、总结

前言

早期的Spring项目需要添加需要配置繁琐的xml,比如MVC、事务、数据库连接等繁琐的配置。Spring Boot的出现就无需这些繁琐的配置,因为Spring Boot基于约定大于配置的理念,在项目启动时候,将约定的配置类自动装配到IOC容器里。

这些都因为Spring Boot有自动装配的特性。

接下来将会逐步从源码跟踪进去,一步步掀开自动装配的面纱。

1、main入口

在SpringBoot项目的启动类中,注解SpringBootApplication是必须需要添加的,既然是从这里启动的,那么自动装配的操作应该也是在启动的时候去执行的吧,我们一步步挖进去一探究竟。

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

2、@SpringBootApplication

点进来@SpringBootApplication注解之后会发现,这玩意头顶怎么挂着这么多注解的。不要着急,与Bean注入也只有三个相关,而自动装配的核心注解也是只有一个:

  • @SpringBootConfiguration:继承自Spring的 @Configuration 注解,作用也大致相同,支持在入口处通过@Bean等注解手动配置一下 Bean 加入到容器中;
  • @EnableAutoConfiguration:顾名思义,这玩意就是用来自动装配的,都写在人家脸上了,接下来主要的介绍核心也是该注解;
  • @ComponentScan:告诉Spring需要扫描哪些包或类,如果不设值的话默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,这也是为什么放置启动类位置有要求的原因。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {// 省略该注解内属性和方法// ......
}

3、@EnableAutoConfiguration

点进来@EnableAutoConfiguration注解,去掉那些有的没的注解,可以初步发现与自动装配有关的应该是有两个,分别是注解@AutoConfigurationPackage和导入的类AutoConfigurationImportSelector

点进去注解@AutoConfigurationPackage发现里面没有什么有用的信息,其作用是将添加该注解的类所在的package 作为自动装配 package 进行管理,感兴趣的小伙伴可以自行点进去查看。

AutoConfigurationImportSelector作为被导入的类,也是实现自动装配的核心类之一,接下来将会点进去查看其细节。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {// 省略该注解内属性和方法// ......
}

4、AutoConfigurationImportSelector

4.1、selectImports()

AutoConfigurationImportSelector类中,自动装配核心方法为selectImports(),在其文档中是这么介绍该方法的:

Select and return the names of which class(es) should be imported based on the AnnotationMetadata of the importing @Configuration class.

Returns: the class names, or an empty array if none

根据@Configuration中的AnnotationMetadata,选择并返回应该被导入的类的名称。

返回:类名,如果不存在则返回空数组

可以看到从这里便已经获取被设置需要自动装配的类的信息了,从源码中可以看到在selectImports()中的核心方法应该是getAutoConfigurationEntry()

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// 省略类中成员属性...@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}// 省略类中其余方法...
}

4.2、getAutoConfigurationEntry()

getAutoConfigurationEntry()方法同样处于AutoConfigurationImportSelector类中,在刚方法中主要返回的是已经配置好的配置数组,该配置数组被包装在类中,其中AutoConfigurationImportSelector.AutoConfigurationEntry的介绍如下:

Create an entry with the configurations that were contributed and their exclusions.

Params: configurations – the configurations that should be imported exclusions – the exclusions that were applied to the original list

使用所提供的配置及其排除项创建一个条目。

参数:configurations ——应该导入的配置 exclusions – 应用于原始列表的排除项

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);// 获取应考虑的自动配置类名称List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);configurations = removeDuplicates(configurations);// 获取被排除的数据Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);// 封装应考虑的自动配置类名称和被排除的数据return new AutoConfigurationEntry(configurations, exclusions);
}

4.3、getCandidateConfigurations()

方法getCandidateConfigurations()同样处于AutoConfigurationImportSelector类中,该方法主要用于获取应考虑的自动装配类名称,而获取候选项的方法为其中的loadFactoryNames()方法。

Return the auto-configuration class names that should be considered. By default this method will load candidates using ImportCandidates with getSpringFactoriesLoaderFactoryClass().

返回应考虑的自动配置类名称。默认情况下,此方法将使用ImportCandidates和getSpringFactoriesLoaderFactoryClass()加载候选项。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = new ArrayList<>(// 获取候选项SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;
}

4.4、loadFactoryNames()

挖到这里其实就差不多到头了,再往下就不太礼貌了。

在这里干的活在官方给出的文档中都说的很简单明了了:

Load the fully qualified class names of factory implementations of the given type from “META-INF/spring.factories”, using the given class loader.

As of Spring Framework 5.3, if a particular implementation class name is discovered more than once for the given factory type, duplicates will be ignored.

使用给定的类加载器,从"META-INF/spring.factories"加载给定类型的工厂实现的完全限定类名。

从 Spring Framework 5.3 开始,如果针对给定工厂类型多次发现特定实现类名称,则将忽略重复项。

从介绍中就可以看到所谓的自动装配,就是将META-INF/spring.factories中写明白的内容给加载出来,同时连上面提及到的排除项都写明白在这里了,而源码也是对官方文档进行直接翻译了,猜测是出于封装性和代码简洁度考虑,开发人员将核心逻辑封装成了私有方法loadSpringFactories()

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {ClassLoader classLoaderToUse = classLoader;if (classLoaderToUse == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();// 核心流程return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = cache.get(classLoader);if (result != null) {return result;}result = new HashMap<>();try {// 获取资源,常量FACTORIES_RESOURCE_LOCATION的值为META-INF/spring.factoriesEnumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);// 逐一处理加载到的资源while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);// 装配属性Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();String[] factoryImplementationNames =StringUtils.commaDelimitedListToStringArray((String) entry.getValue());for (String factoryImplementationName : factoryImplementationNames) {result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()).add(factoryImplementationName.trim());}}}// Replace all lists with unmodifiable lists containing unique elementsresult.replaceAll((factoryType, implementations) -> implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));cache.put(classLoader, result);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}return result;
}

5、META-INF/spring.factories

spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默认配置了很多aoto-configuration,如下:

image-20230802165739779

WebMvcAutoConfiguration为例:

package org.springframework.boot.autoconfigure.web.servlet;@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {@Bean@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}@Bean@ConditionalOnMissingBean(HttpPutFormContentFilter.class)@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {return new OrderedHttpPutFormContentFilter();}......etc
}

这里有一个地方不知道大家有没有注意到,有大部分的注解前面都有Conditional字样的,这其实也是SpringBoot的强大之处之一,其使用了 Spring 4 框架的新特性:@Conditional注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。Conditional大家族如下:

注解描述
@ConditionalOnBean当容器中存在指定的Bean时生效
@ConditionalOnClass当类路径中存在指定的类时生效
@ConditionalOnExpression通过SpEL表达式指定条件,满足条件时生效
@ConditionalOnMissingBean当容器中不存在指定的Bean时生效
@ConditionalOnMissingClass当类路径中不存在指定的类时生效
@ConditionalOnNotWebApplication当应用程序不是Web应用时生效
@ConditionalOnResource当指定的资源存在于类路径中时生效
@ConditionalOnWebApplication当应用程序是Web应用时生效

6、总结

SpringBoot自动配置原理如下:

  1. @EnableAutoConfiguration 注解导入 AutoConfigurationImportSelector 类;
  2. 执行 selectImports() 方法调用 SpringFactoriesLoader.loadFactoryNames() 扫描所有 jar 下面的对应的 META-INF/spring.factories 文件;
  3. 限定为 @EnableAutoConfiguration 对应的 value,将这些装配条件的装配到 IOC 容器中。

自动装配简单来说就是自动将第三方的组件的 bean 装载到 IOC 容器内,不需要再去写 bean 相关的配置,符合约定大于配置理念。SpringBoot 基于约定大于配置的理念,配置如果没有额外的配置的话,就给按照默认的配置使用约定的默认值,按照约定配置到 IOC 容器中,无需开发人员手动添加配置,加快开发效率。

同时,对于自己开发SDK时,也可利用 SpringBoot 自动装配原理,编写自己的 META-INF/spring.factories 文件,从而将某些特定需求的类生成 Bean 放入容器中。

相关文章:

【Spring】深究SpringBoot自动装配原理

文章目录 前言1、main入口2、SpringBootApplication3、EnableAutoConfiguration4、AutoConfigurationImportSelector4.1、selectImports()4.2、getAutoConfigurationEntry()4.3、getCandidateConfigurations()4.4、loadFactoryNames() 5、META-INF/spring.factories6、总结 前言…...

阿里云负载均衡SLB网络型NLB负载均衡架构性能详解

阿里云网络型负载均衡NLB是阿里云推出的新一代四层负载均衡&#xff0c;支持超高性能和自动弹性能力&#xff0c;单实例可以达到1亿并发连接&#xff0c;帮您轻松应对高并发业务。网络型负载均衡NLB具有超强性能、自动弹性伸缩、高可用、TCPSSL卸载、多场景流量分发和丰富的高级…...

JavaScript学习 -- SM4算法应用实例

SM4算法&#xff0c;也被称为国密算法&#xff0c;是中国公布的一种高效且安全的对称加密算法。在JavaScript中&#xff0c;我们可以通过使用CryptoJS库来实现SM4算法的加密和解密。本篇博客将为您介绍如何在JavaScript中使用SM4算法&#xff0c;并提供一个实际的案例。 首先&…...

【JVM】什么是双亲委派机制

文章目录 1、类加载机制2、双亲委派模型2.1、介绍2.2、为什么需要双亲委派2.3、源码解析 3、破坏双亲委派3.1、介绍3.2、破坏实现3.3、破坏双亲委派的例子 4、线程上下文类加载器 1、类加载机制 类加载阶段分为加载、连接、初始化三个阶段&#xff0c;而加载阶段需要通过类的全…...

网络安全 Day24-select高级用法和多表连接

select高级用法和多表连接 1. select 多子句单表高级实践1.1 select 多子句高级语法1.2 聚合函数1.3 group by 实践1.4 having 筛选1.5 order by 排序1.6 limit 2. 多表连接 1. select 多子句单表高级实践 1.1 select 多子句高级语法 where 和 having 区别是后者是分组后进行…...

JUC并发编程之volatile详解

目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中&#xff0c;多线程操作共享的变量时&a…...

swing布局详解

1. 布局管理器接口 &#xff08;1&#xff09;说明 布局管理器接口为LayoutManager和LayoutManager2&#xff0c;LayoutManager2是LayoutManager的子类。 &#xff08;2&#xff09;常用方法 方法描述LayoutManageraddLayoutComponent(String name, Component comp) removeL…...

el-table某一列嵌套使用el-popover,使用click触发,导致页面下拉框组件无法触发弹框关闭(解决办法)

在弹框触发的方法里加上document.body.click() 即可 尝试了很多其他的方法都没用&#xff0c;只有这个解决了 完整代码&#xff1a; <el-select change"sourceChange" clearable ><el-optionv-for"option in list1":key"option.code":…...

正泰电力携手图扑:VR 变电站事故追忆反演

VR(Virtual Reality&#xff0c;虚拟现实)技术作为近年来快速发展的一项新技术&#xff0c;具有广泛的应用前景&#xff0c;支持融合人工智能、机器学习、大数据等技术&#xff0c;实现更加智能化、个性化的应用。在电力能源领域&#xff0c;VR 技术在高性能计算机和专有设备支…...

报错 -bash: wget: command not found

1、报错 -bash: wget: command not found 可以重装 wget 工具&#xff1a; 卸载 wget 工具 yum remove wget下载 wget 工具 yum -y install wget最后尝试 wget “url” 又OK了&#xff0c;一般是原来的wget初始化有文件损坏造成的。...

HashMap扩容和Redis中Dict 扩容

扩容时机&#xff1a; Hash Map&#xff1a;要在某个临界点进行扩容处理&#xff0c;该临界点就是HashMap中元素的数量在数值上等于threshold&#xff08;table数组长度*加载因子&#xff09; Dict&#xff1a; 当每次新增键值对的时 , 会检测 负载因子(LoadFactor) , 判断以…...

【Redis】内存数据库Redis进阶(Redis持久化)

目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…...

在PHP8中检测数据类型-PHP8知识详解

在PHP 8中&#xff0c;可以使用多种方法来检测数据类型。以下是常用的四种方法&#xff1a;使用 gettype() 函数、使用 is_* 系列函数、使用 get_debug_type() 函数、使用 get_class() 函数。 一、使用 gettype() 函数 gettype() 函数返回给定变量的数据类型。例如&#xff1a…...

​​​amoeba实现MySQL读写分离

​​​amoeba实现MySQL读写分离 准备环境&#xff1a;主机A和主机B作主从配置&#xff0c;IP地址为192.168.131.129和192.168.131.130&#xff0c;主机C作为中间件&#xff0c;也就是作为代理服务器&#xff0c;IP地址为192.168.131.136。三台服务器操作系统为RHEL6.4 x86_64,为…...

angr学习-入门篇

前言&#xff1a; 资源链接&#xff1a;GitHub - jakespringer/angr_ctf&#xff08;题库仓库&#xff0c;里面有个讲解angr的PPT&#xff0c;里面有官方的题解很详细&#xff09; GitHub - Hustcw/Angr_Tutorial_For_CTF: angr tutorial for ctf 安装&#xff1a; 关于angr…...

基于java SpringBoot和HTML的博客系统

随着网络技术渗透到社会生活的各个方面&#xff0c;传统的交流方式也面临着变化。互联网是一个非常重要的方向。基于Web技术的网络考试系统可以在全球范围内使用互联网&#xff0c;可以在本地或异地进行通信&#xff0c;大大提高了通信和交换的灵活性。在当今高速发展的互联网时…...

动态sql以及常用的标签

什么是动态sql&#xff1a; 指根据不同的条件生成不同的sql 搭建环境&#xff1a; 建表&#xff1a; create table blog( id varchar(50) not null comment 博客id, title varchar(100) not null comment 博客标题, author varchar(30) not null comment 博客作者, create_ti…...

DID以及社交网络中的ZKP

1. 引言 本文关键术语为&#xff1a; Decentralized Identity (DID&#xff0c;去中心化身份) or self-sovereign identity (SSI&#xff0c;自治身份) &#xff1a;是一个基于开放标准的框架&#xff0c;使用自主、独立的标识符和可验证证书&#xff0c;实现可信的数据交换。…...

基于SWAT-MODFLOW地表水与地下水耦合

耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果&#xff0c;SWAT作为一个地表水模型可以较好的模拟主要的水文过程&#xff0c;包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等&#xff0c;但是对于地下水部分的模拟相对粗糙&#xff0c;考虑到SWAT…...

2023拒绝内卷!两年转行网络安全真实看法!

我目前转行网络安全两年&#xff0c;没啥天分&#xff0c;全靠努力&#xff0c;基本能够得上中级的水平了。看到大家对转行网络安全挺感兴趣&#xff0c;也有挺多争议&#xff0c;想把我的建议和经验告诉大家。 有很多人觉得网络安全已经饱和了&#xff0c;现在选择这个工作&a…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...