SpringBoot自动配置原理分析
作者:宁在春 发布时间:2022-03-02 17:42:51
前言
SpringBoot
是我们经常使用的框架,那么你能不能针对SpringBoot
实现自动配置做一个详细的介绍。如果可以的话,能不能画一下实现自动配置的流程图。牵扯到哪些关键类,以及哪些关键点。下面我们一起来看看吧!!
阅读完本文:
你能知道 SpringBoot 启动时的自动配置的原理知识
你能知道 SpringBoot 启动时的自动配置的流程
以及对于 SpringBoot 一些常用注解的了解
一步一步 debug 从浅到深。
注意
:本文的 SpringBoot 版本为 2.5.2
一、启动类
前言什么的,就不说了,大家都会用的,我们直接从 SpringBoot
启动类说起。
@SpringBootApplication
public class Hello {
public static void main(String[] args) {
SpringApplication.run(Hello.class);
}
}
@SpringBootApplication
标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;是我们研究的重点!!!它的本质是一个组合注解,我们点进去,看看javadoc
上是怎么写的,分析从浅到深,从粗略到详细。
我们点进去看:
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
Javadoc
上是这么写的
表示声明一个或多个@Bean方法并触发 auto-configuration 和 component scanning 的 configuration 类。 这是一个方便的注解,相当于声明了 @Configuration 、 @EnableAutoConfiguration 和@ComponentScan 。
---为什么它能集成这么多的注解的功能呢?
是在于它上面的 @Inherited
注解, @Inherited
表示自动继承注解类型。
这里的最重要的两个注解是 @SpringBootConfiguration
和 @EnableAutoConfiguration
。
1.1、@SpringBootConfiguration
我们先点进去看看 @SpringBootConfiguration
注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {}。
1.2、@EnableAutoConfiguration
再看看 @EnableAutoConfiguration
.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
1.3、@ComponentScan
@ComponentScan
:配置用于 Configuration 类的组件扫描指令。 提供与 Spring XML
的 <context:component-scan>
元素并行的支持。 可以 basePackageClasses
或basePackages
( 或其别名value )来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描。
作为了解,不是本文重点。
1.4、探究方向
主要探究图中位于中间部分那条主线,其他只会稍做讲解。
二、@SpringBootConfiguration
我们刚刚已经简单看了一下 @SpringBootConfiguration
啦。
@Configuration
@Indexed
public @interface SpringBootConfiguration {}
它是 springboot
的配置类,标注在某个类上,表示这是一个 springboot
的配置类。
我们在这看到 @Configuration
,这个注解我们在 Spring
中就已经看到过了,它的意思就是将一个类标注为 Spring
的配置类,相当于之前 Spring
中的 xml
文件,可以向容器中注入组件。
不是探究重点。
三、@EnableAutoConfiguration
我们来看看这玩意,它的字面意思就是:自动导入配置。
@Inherited
@AutoConfigurationPackage ////自动导包
@Import(AutoConfigurationImportSelector.class) ////自动配置导入选择
public @interface EnableAutoConfiguration {}
从这里顾名思义就能猜到这里肯定是跟自动配置有关系的。
我们接着来看看这上面的两个注解 @AutoConfigurationPackage
和 @Import(AutoConfigurationImportSelector.class)
,这两个才是我们研究的重点。
3.1、@AutoConfigurationPackage
点进去一看:
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
@Import
为 spring
的注解,导入一个配置文件,在 springboot
中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.Registrar.class
执行逻辑来决定的。
往下👇看:Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
在这个地方我们可以打个断点,看看 new PackageImports(metadata).getPackageNames().toArray(new String[0])
它是一个什么值。
我们用 Evaluate
计算 new PackageImports(metadata).getPackageNames().toArray(new String[0])
出来可以看到就是 com.crush.hello
,当前启动类所在的包。
继续往下看的话就是和 Spring 注册相关了,更深入 xdm 可以继续 debug。
在这里我们可以得到一个小小的结论:
@AutoConfigurationPackage 这个注解本身的含义就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描到 spring 容器中。
如果将一个 Controller
放到 com.crush.hello
以外就不会被扫描到了,就会报错。
3.2、@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector
开启自动配置类的导包的选择器(导入哪些组件的选择器)
我们点进 AutoConfigurationImportSelector
类来看看,有哪些重点知识,这个类中存在方法可以帮我们获取所有的配置
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
/**选择需要导入的组件 ,*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry 。
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);
}
}
我们看看这个断点,configurations
数组长度为131,并且文件后缀名都为 **AutoConfiguration
这里的意思是将所有需要导入的组件以全类名的方式返回,并添加到容器中,最终会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件。有了自动配置,就不需要我们自己手写了。
3.2.1、getCandidateConfigurations()
我们还需要思考一下,这些配置都从 getCandidateConfigurations 方法中获取,这个方法可以用来获取所有候选的配置,那么这些候选的配置又是从哪来的呢?
一步一步点进去:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这里有个 loadFactoryNames 方法 执行的时候还传了两个参数,一个是BeanClassLoader ,另一个是 getSpringFactoriesLoaderFactoryClass() 我们一起看看
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()
方法,这里传过去的是
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
这个 EnableAutoConfiguration
是不是特别眼熟,(我们探究的起点 @EnableAutoConfiguration
,有没有感觉自己离答案越来越近啦)
我们再看看 loadFactoryNames()
方法带着它去做了什么处理:
先是将 EnableAutoConfiguration.class
传给了 factoryType
,然后 .getName( )
,所以factoryTypeName
值为 EnableAutoConfiguration
。
3.2.2、loadSpringFactories()
接下里又开始调用 loadSpringFactories
方法
这里的 FACTORIES_RESOURCE_LOCATION
在上面有定义:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
我们再回到 getCandidateConfigurations
方法处。
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.");
这句断言的意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“
这个 META-INF/spring.factories
在哪里呢?
里面的内容:
我们日常用到的,基本上都有一个配置类。
比如 webmvc,
我们点进 WebMvcProperties 类中去看一下:
那这里到底是要干什么呢?
这里的意思首先是把这个文件的 urls 拿到之后并把这些 urls 每一个遍历,最终把这些文件整成一个properties 对象,loadProperties
方法
然后再从 properties 对象里边获取一些我们需要的值,把这些获取到的值来加载我们最终要返回的这个结果,结果 result 为 map 集合,然后返回到loadFactoryNames
方法中。
然后我们再回到 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
的调用处。
这个 factoryTypeName
值为 EnableAutoConfiguration
因为 loadFactoryNames
方法携带过来的第一个参数为 EnableAutoConfiguration.class
,所以 factoryType
值也为 EnableAutoConfiguration.class
,那么 factoryTypeName
值为 EnableAutoConfiguration
。
那么map集合中 getOrDefault
方法为什么意思呢?意思就是当 Map
集合中有这个 key 时,就使用这个 key值,如果没有就使用默认值 defaultValue
(第二个参数),所以是判断是否包含 EnableAutoConfiguration
看下图,这不就是嘛?
所以就是把 spring-boot-autoconfigure-2.5.2.jar/META-INF/spring.factories
这个文件下的EnableAutoConfiguration
下面所有的组件,每一个 xxxAutoConfiguration
类都是容器中的一个组件,都加入到容器中。加入到容器中之后的作用就是用它们来做自动配置,这就是Springboot
自动配置开始的地方。
只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动
那 spring.factories
中存在那么多的配置,每次启动时都是把它们全部加载吗?
是全部加载嘛?不可能的哈,这谁都知道哈,全部加载启动一个项目不知道要多久去了。它是有选择的。
我们随便点开一个类,都有这个 @ConditionalOnXXX
注解
@Conditional
其实是 spring
底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么整个配置类里边的配置才会生效。
所以在加载自动配置类的时候,并不是将 spring.factories
的配置全部加载进来,而是通过这个注解的判断,如果注解中的类都存在,才会进行加载。
这就是SpringBoot
的自动配置啦.
四、小结
简单总结起来就是:
启动类中有一个 @SpringBootApplication
注解,包含了 @SpringBootConfiguration
、 @EnableAutoConfiguration
, @EnableAutoConfiguration
代表开启自动装配,注解会去 spring-boot-autoconfigure
工程下寻找 META-INF/spring.factories
文件,此文件中列举了所有能够自动装配类的清单,然后自动读取里面的自动装配配置类清单。因为有 @ConditionalOn
条件注解,满足一定条件配置才会生效,否则不生效。 如: @ConditionalOnClass(某类.class)
工程中必须包含一些相关的类时,配置才会生效。所以说当我们的依赖中引入了一些对应的类之后,满足了自动装配的条件后,自动装配才会被触发。
来源:https://juejin.cn/post/7012862822082150413
猜你喜欢
- String类型小数值转为Long类型数值分为小数和整数,当传入的类型为String,需要获取的类型为Long,这时候直接通过Long.va
- AndroidStduio3.0使用gradle将module打包jar文件,首先需要安装gradle。打开控制台输入  
- 前言在工作中,比如要实现一个功能,前端传什么参数,后端的controller层中怎么接收参数 ,封装成了什么实体对象,有些参数是在URL上使
- Bean的自动装配自动装配说明自动装配是使用spring满足bean依赖的一种方法spring会在应用上下文中为某个bean寻找其依赖的be
- 时间处理相关类:1.java.util.Date:时间类2.java.text.DateFormat:时间格式化类(抽象类),实现类:jav
- 问题springboot 集成springcloud时常常由于版本问题而报错,如下:com.sun.jersey.api.client.Cl
- 1、配置maven环境变量,将maven安装的bin⽬录添加到path路径中(此电脑->属性->高级系统设置->环境变量-
- 最新开发新项目的时候,要做分享项目,要求分享有微信,微信朋友圈,QQ,QQ空间,新浪微博这五个,所分享内容包括,分享纯图片,纯文字,图文类型
- Java * 要想了解Java * ,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有
- java Hibernate多对多映射前言:一、单向多对多 单向多对多的例子用人和职位来举例,一个人可以有多个职位
- 我们平时使用的一些常见队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了De
- 一、前言最近写了个项目,前端还没写,需要部署到服务器给女朋友实现前端,可是不熟悉Linux的我,蹑手蹑脚,真的是每一步都是bug,可谓是步步
- java中Hashmap的get方法map中存储的是键值对,也就是说通过set方法进行参数和值的存储,之后通过get“键”的形式进行值的读取
- monitor概念管程,监视器。在操作系统中,存在着semaphore和mutex,即信号量和互斥量,使用基本的mutex进行开发时,需要小
- Tomcat 如何实现WebSocketWebSocket协议属于HTML5标准,越来越多浏览器已经原生支持WebSocket,它能让客户端
- 背景产品想对多次快速点击做一下优化,想要的效果就是双击不会打开多次但是从开发角度来说,我可以用kotlin的拓展方法来调整这个,但是之前的历
- 1. 前言我们知道,在日常开发中使用的 HashMap 是线程不安全的,而线程安全类 HashTable 和 SynchronizedMap
- File类简介package com.file;import java.io.File;import java.io.IOException
- IOS与网页JS交互随着移动APP的快速迭代开发趋势,越来越多的APP中嵌入了html网页,但在一些大中型APP中,尤其是电商类
- main方法调用spring的service将业务层类配置到Spring中:<bean id="customerServic