一文搞懂!SpringBoot自动装配原理

一、前言

当我们开始使用Spring Boot时,我们会发现只需引入一个web相关的starter依赖,并进行一些简单配置,有的时候甚至可以不用配置(约定大于配置),应用就可以跑起来!这与Spring MVC相比,后者需要我们进行大量的手动配置和解决依赖冲突,显得繁琐复杂。Spring Boot是如何解决这一点,让我们能够更加专注于业务逻辑的开发呢?这得益于Spring Boot的核心特性——自动装配

学习参考视频:SpringBoot3
参考资料:SpringBoot3自动装配原理

二、自动装配是什么

Spring Boot的自动装配实际上是从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中获取到对应的需要进行自动装配的类,并生成相应的Bean对象,然后将它们交给Spring容器进行管理

三、源码解析

这里我从项目的开始一步步带领大家深入分析自动装配的原理

1.导入starter场景

我们这里以spring-boot-starter-web为例

在这里插入图片描述
当导入starter-web场景启动器后,我们就导入了和Web相关场景的所有依赖:starter-tomcatstarter-jsonstarter-tomcatspring-webmvcspring-boot-starter

其中spring-boot-starter核心场景启动器,是所有starterstarter,基础核心starter(每个场景启动器都会引入一个核心场景启动器
在这里插入图片描述
在核心场景启动器spring-boot-starter中又引入了spring-boot-autoconfigure包,这个包里面囊括了SpringBoot官方全场景的所有配置
在这里插入图片描述
只要spring-boot-autoconfigure这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了,但是SpringBoot默认却扫描不到 spring-boot-autoconfigure下写好的配置类。(SpringBoot默认只扫描主程序所在的包及其子包

思考:官方写好的配置类默认却扫描不到,那该怎么让它生效呢?
跟随我的脚步,进行深入分析主程序:@SpringBootApplication,相信你会找到答案!

2.深入剖析启动类

SpringBoot的启动类是一切的开始,我可以发现在main方法上有个注解@SpringBootApplication
在这里插入图片描述
在这里插入图片描述

@SpringBootApplication是一个复合注解,由以下三个注解组合而成,@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
在这里插入图片描述

  • @SpringBootConfiguration :标记当前类为SpringBoot配置类
  • @EnableAutoConfiguration:开启自动配置的核心注解
  • @ComponentScan :组件扫描

注意:上图我在三个注解前标注的序号,这是主程序运行起来后的执行顺序
下面我将逐一分析这个3个注解

@SpringBootConfiguration

@SpringBootConfiguration就是@Configuration的一个衍生注解
主要作用:标记该类为SpringBoot的配置类,并交给SpringIOC容器管理
在这里插入图片描述

@EnableAutoConfiguration

@EnableAutoConfiguration,顾名思义就是:开启自动配置,也是一个复合注解,由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)组合而成
在这里插入图片描述

@AutoConfigurationPackage

@AutoConfigurationPackage主要作用:扫描主程序包,加载自己的组件

@Import:导入第三方的组件,可以导入三种类型的类

  1. 导入@Configuration注解的配置类:容器中就会自动注册这个组件,ID默认为全类名
  2. 导入ImportSelector 实现类:通过实现ImportSelector 类,实现selectImports方法,返回需要导入组件的全类名数组
  3. 导入ImportBeanDefinitionRegistrar实现类:通过实现中 ImportBeanDefinitionRegistrar类,实现registerBeanDefinitions 方法手动注册Bean到容器中
    详细内容可见@Import注解 -【Spring底层原理】Spring中Import注解源码解析

在这里插入图片描述
在这个注解利用 @Import(AutoConfigurationPackages.Registrar.class)把主程序所在包及其子包的所有组件导入进来

利用@Import ,在springboot中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class的内部类Registrar.class 执行逻辑来决定是如何导入的

进入到AutoConfigurationPackages类中找到内部类Registrar,并在图中位置打上断点
在这里插入图片描述

ImportBeanDefinitionRegistrar 接口:

  • 这个接口是 Spring 框架提供的,用于在配置类中动态注册 Bean 定义
  • 实现了这个接口的类可以通过 @Import 注解导入到 Spring 容器中

接下来我们利用小工具,来看看new PackageImports(metadata).getPackageNames()的值
在这里插入图片描述在这里插入图片描述
运行后我们可以看到new PackageImports(metadata).getPackageNames()的值为:com.weyang当前启动类所在的包名,
即把主程序所在的包的所有组件导入进来。

结论:@AutoConfigurationPackage就是将主配置类(@SpringBootApplication标记的类)所在包及其子包中的所有组件都扫描注册到Spring容器中

@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector开启自动配置类的导包的选择器,确定哪些自动配置类应该被加载到 Spring 容器中。具体来说,它实现了 DeferredImportSelector 接口。
在这里插入图片描述

DeferredImportSelectorImportSelector 的区别
在这里插入图片描述在这里插入图片描述
详细介绍:Spring中DeferredImportSelector源码解析
补充说明
在这里插入图片描述
由于AutoConfigurationImportSelector没有直接实现ImportSelector接口,而是实现的DeferredImportSelector接口,SpringBoot对于DeferredImportSelector接口的实现类有另外一组处理流程,因此在自动装配的流程中并不会去调用selectImports()方法

在这里插入图片描述

getImportGroup方法:根据这个方法得到具体的 Group 实现类

我们先在AutoConfigurationImportSelector中找到getImportGroup方法,通过这个方法可以知道DeferredImportSelector实现类对应的Group类AutoConfigurationGroup
在这里插入图片描述
调用Group实现类AutoConfigurationGroup中的,process方法,并调用AutoConfigurationImportSelectorgetAutoConfigurationEntry方法,获取所有的自动配置类
在这里插入图片描述

getAutoConfigurationEntry方法:获取所有的自动配置类

在这里插入图片描述
根据导入的 @Configuration 类AnnotationMetadata 返回一个 AutoConfigurationImportSelector.AutoConfigurationEntry 对象。这个对象包含了应该被导入的自动配置类列表以及被排除的配置类列表
getCandidateConfigurations(annotationMetadata, attributes)这里打断点查看,可以看到configurations数组长度为181,并且文件后缀名都为 ***AutoConfiguration
在这里插入图片描述
进入getCandidateConfigurations(annotationMetadata, attributes)看看这个181个配置类从哪里来的
在这里插入图片描述
通过ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())方法,从类路径中加载所有标记了 @AutoConfiguration 注解的类,并返回这些类的全限定名列表,同时通过 Assert.notEmpty 方法确保列表不为空,否则抛出异常并提示检查配置文件

Assert.notEmpty(configurations,
				"No auto configuration classes found in "
						+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");

意思是:在“META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。” ------ 在2.7.0版本之前存放的地址是META-INF/spring.factories
我们点击进入load方法,就可以看到配置类存放地址是如何来的了!
在这里插入图片描述
我们可以看到扫描SPI文件地址:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
然后调用findUrlsInClasspath方法在类路径中查找指定路径的资源,遍历找到的资源 URL,调用 readCandidateConfigurations 方法读取每个资源文件中的候选类名,并添加到 importCandidates 列表中。

注意:这里会加载所有jar包下的classpath路径下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,这样的文件不止一个

现在我们进行回到AutoConfigurationImportSelectorgetAutoConfigurationEntry方法
在这里插入图片描述
经过removeDuplicates方法去除重复的配置类,getExclusions方法去除手动排除的类,getConfigurationClassFilter方法根据条件判断,过滤掉不符合条件的配置类后,在我的这个项目中实际就剩下自动配置类数量为70个。
在这里插入图片描述

SPI机制:
Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想
是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
● SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库> > 连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而> > 无需在代码中硬编码这些组件的实现类。
● 在Java中,SPI的实现方式是通过在META-INF/services目录下创建一个以服务接口全限定名为名字的文件,文件中包含实> 现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定> 的类名来加载实现类。
● 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。

在这里插入图片描述
根据刚刚找到的地址找到配置文件,我们可以看到在这个位置存的内容,就是SpringBoot启动默认加载152个自动配置类xxxAutoConfiguration,因为演示的项目中还引入了其他的依赖,例如druid、mybatisplus等,所以刚刚显示的是181。
虽然SpringBoot默认导入了152个自动配置类, 并不是这152个自动配置类都能生效,每一个自动配置类,都有条件注解@ConditionalOnxxx,只有条件成立,才能生效

常用的@ConditionalOnXxx注解:

@ConditionalOnClass : classpath中存在该类时起效
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效

@ConditionalOnXxx其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。

在152个自动配置类的全类名中,我们找到Spring MVC 框架中的核心组件DispatcherServlet的自动配置类DispatcherServletAutoConfiguration
在这里插入图片描述
在 DispatcherServletAutoConfiguration 这个类上添加了一个@AutoConfiguration@ConditionalOnClass(DispatcherServlet.class)注解
在这里插入图片描述
其中@AutoConfiguration注解就是对@Configuration的一个封装,为了让其更加见名知意,说明这个类就是为了完成自动配置的。

@AutoConfiguration 注解是 Spring Boot 提供的一个重要注解,用于标记一个配置类,使其成为自动配置的一部分。它的主要作用和特点如下:

  1. 自动配置:
    标记的类会被 Spring Boot 的自动配置机制识别。
    Spring Boot 会根据应用的依赖和环境自动应用这些配置,减少手动配置的工作量。
  2. 条件化配置:
    通常与 @Conditional 注解一起使用。
    使得配置类只有在满足特定条件时才会被加载和应用,提高了配置的灵活性和适应性。
  3. 见名知意:
    @AutoConfiguration 注解使得配置类的用途更加明确和直观,表明该类是为了完成自动配置> 的。
    开发人员可以一目了然地知道某个类的作用,提高代码的可读性和可维护性。
  4. 集成第三方库:
    通过 @AutoConfiguration 注解,可以轻松地集成第三方库,而不需要手动编写大量的配置代码。

另外,还一个注解@ConditionalOnClass(DispatcherServlet.class),意思是如果我们的环境中有DispatcherServlet,才会注入 DispatcherServletAutoConfiguration 的bean对象,否则不会生效
在这个自动配置类中,我们还可以找到一个@EnableConfigurationProperties(xxxProperties.class)
在这里插入图片描述

@EnableConfigurationProperties VS @ConfigurationProperties

  • 在@ConfigurationProperties的使用,把配置类的属性与yml配置文件绑定起来的时候,还需要加上@Component注解才能绑定并注入IOC容器中,若不加上@Component,则会无效。
  • @EnableConfigurationProperties,则是快速实现属性配置绑定,只要加上该注解,即可完成属性绑定,无需加@Component,更多用于导入第三方写好的组件进行属性绑定。
    因为SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用。因为组件都扫描不进来,此时使用这个注解就可以快速进行属性绑定并把组件注册进容器

结论:
SpringBoot通过@Conditional注解,实现自动配置类的按需生效。并通过@EnableConfigurationProperties注解实现组件的核心参数与配置文件进行绑定,从而实现只需要该配置文件的值,对应核心组件的底层参数都会被修改的效果。

@ComponentScan

组件扫描:排除一些组件(排除前面已经扫描进来的配置类、和自动配置类)

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

四、总结

在这里插入图片描述
核心流程总结:

  1. 导入starter,就会导入autoconfigure包。
  2. autoconfigure 包里面 有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定的所有启动要加载的自动配置类
  3. @EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
  4. xxxAutoConfiguration给容器中导入一堆组件,组件都是从 xxxProperties中提取属性值
  5. xxxProperties又是和配置文件进行了绑定
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值