SpringBoot自动装配源码
前几天,面试的时候被问到了SpringBoot的自动装配的原理。趁着五一的假期,就来整理一下这个流程。
我这里使用的是idea创建的最简单的SpringBoot项目。
我们都知道,main方法是java的启动入口,我们在开发SpringBoot项目的时候,他的启动类如下所示:
/**
* @SpringBootApplication用来标注一个主程序类,说明这是一个springboot应用
*/
@SpringBootApplication
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
从上面代码可以看出,SpringBoot的启动类中最主要的就是:@SpringBootApplication和SpringApplication.run()方法。但是SpringBoot是如何找到当前程序的主类,并且获取类上的注解的呢?
1.加载main主类
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
从上面的代码可以看出,推断出主应用程序的类,通过跟踪执行过程中的堆栈信息,找到main函数之后,通过Class.forName的方式反射生成对应的对象(SpringbootdemoApplication)。
2.执行run方法
从run方法开始,一直到prepareContext方法之前,都是在做准备工作
public ConfigurableApplicationContext run(String... args) {
// 创建StopWatch实例,用来记录SpringBoot的启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 创建为null的上下文对象
ConfigurableApplicationContext context = null;
// 创建异常报告器
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 获取监听器,读取META-INF/spring.factories文件中SpringApplicationRunListeners类型存入到集合中
SpringApplicationRunListeners listeners = getRunListeners(args);
// 循环调用监听starting的方法
listeners.starting();
try {
// 获取启动时传入的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 构建当前环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 根据环境信息配置要忽略的bean
configureIgnoreBeanInfo(environment);
// 打印SpringBoot的版本和banner,如果需要修改banner,可以在resources下面新建一个banner.txt文件,将内容拷贝进去
Banner printedBanner = printBanner(environment);
// 使用策略方法创建上下文对象,分为以下三种类型的:
// 1.SERVLET:基于servlet的web应用程序运行,并启动嵌入式的servlet web服务器
// 2.REACTIVE:基于反应式web应用程序运行,并启动嵌入式的反应式web服务器
// 3.NONE:不以web应用程序运行,也不以嵌入式的web应用程序运行
// 这一步会创建5个bean实例,放在下图中的beanDefinitionMap中
context = createApplicationContext();
// 获取异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 完成整个容器的创建和启动以及bean的注入功能,
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 最终会调用AbstractApplicationContext#refresh的方法,实际上就是SpringIOC容器的创建过程,并且会进行自动装配,在没有使用外部Tomcat项目中,还会在这里创建内置Tomcat WebServer 并启动
refreshContext(context);
// 在上下文刷新后调用,定义一个空的模板方法,给其他子类实现重写
afterRefresh(context, applicationArguments);
// StopWatch停止计时,打印springboot启动花费的时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布应用上下文启动完成时间,触发所有Listener监听器的start方法
listeners.started(context);
// 执行所有的Runner运行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布应用上下文就绪事件,触发监听器的running方法
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
prepareContext这个方法前面也是在给应用程序的创建做准备工作(环境变量,初始化参数,发布监听事件,创建bean工厂);
// Load the sources;sources可以是xml文件,也可以是配置类。在这里是使用的配置类(SpringbootDemoApplication)
Set<Object> sources = getAllSources();
// 加载各种bean对象到当前应用程序上下文,即 将SpringbootdemoApplication加载到ApplicationContext中
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
如图所示,在load方法执行前后的beanDefinitionMap的对比,load方法执行后,将SpringbootdemoApplication加载到了bean工厂中了。
3.自动装配
到这里,SpringBoot已经将SpringbootdemoApplication这个启动类加载进bean容器中了。但是SpringBoot是如何引入项目需要的starter呢?
接着前面run方法中的refreshContext(context);这个方法,一路往下找,会发现调用了ConfigurableApplicationContext.refresh()方法,而这个方法是一个模板方法(如下图),因为我们这里并不是使用的SERVLET和嵌入式的方式运行程序,所以调用的是第一个类中的方法,即 AbstractApplicationContext.refresh();在这个方法中,invokeBeanFactoryPostProcessors(beanFactory);这个方法就会执行自动装配的逻辑。
顺着下面的过程:
1.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); AbstractApplicationContext#746行
2.invokeBeanDefinitionRegistryPostProcessors(); PostProcessorRegistrationDelegate#112行
3.parser.parse(candidates); ConfigurationClassPostProcessor#331行
4.因为主类是使用注解的方式注册的bean,所有会走ConfigurationClassParser的第175行的parse方法。
5.processImports(configClass, sourceClass, getImports(sourceClass), filter, true); ConfigurationClassParser#311
在这个方法中,通过getImports方法,其内部的 collectImports(sourceClass, imports, visited) 通过递归的方式收集所有声明的@Import导入的类。在这里有一下两个结果值:
1. AutoConfigurationPackage注解导入的AutoConfigurationPackages.Registrar.class
2. EnableAutoConfiguration注解导入的AutoConfigurationImportSelector.class
这样就找到了我们常说的AutoConfigurationImportSelector这个类了。
6.第5步解析configration完之后,回到ConfigurationClassParser#193行
this.deferredImportSelectorHandler.process();在这个方法里面实现自动导入的逻辑,点击往下,会找到下面的这一行代码,ConfigurationClassParser#809行,通过getImports()获取需要自动装配的类。
7.在第6步的getImports()方法中,点击往下,会找到AutoConfigurationImportSelector#433行。
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
进入到getAutoConfigurationEntry()方法中找到123行的 getCandidateConfigurations():
这个方法里面的 loadFactoryNames()方法,能发现它读取META-INF/spring.factories下的@EnableAutoConfiguration的配置类,这个方法在run方法之中多次调用,根据传参不一样,从META-INF/spring.factories中获取的内容也不一样。
从文件中得到了133个配置类,紧接着在进行去重,排除与过滤(并不是所有的都需要)之后,就得到了需要装配的类,我这里最后还剩26个。
到这里就将需要装配的类都已经识别到了。
参考文章:
https://blog.csdn.net/j080624/article/details/80764031
SpringBoot自动装配源码的更多相关文章
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- SpringBoot自动装配源码解析
序:众所周知spring-boot入门容易精通难,说到底spring-boot是对spring已有的各种技术的整合封装,因为封装了所以使用简单,也因为封装了所以越来越多的"拿来主义" ...
- SpringBoot自动装配-源码分析
1. 简介 通过源码探究SpringBoot的自动装配功能. 2. 核心代码 2.1 启动类 我们都知道SpringBoot项目创建好后,会自动生成一个当前模块的启动类.如下: import org. ...
- SpringBoot自动配置源码调试
之前对SpringBoot的自动配置原理进行了较为详细的介绍(https://www.cnblogs.com/stm32stm32/p/10560933.html),接下来就对自动配置进行源码调试,探 ...
- SpringBoot启动代码和自动装配源码分析
随着互联网的快速发展,各种组件层出不穷,需要框架集成的组件越来越多.每一种组件与Spring容器整合需要实现相关代码.SpringMVC框架配置由于太过于繁琐和依赖XML文件:为了方便快速集成第三 ...
- springboot自动配置源码解析
springboot版本:2.1.6.RELEASE SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfig ...
- 原创001 | 搭上SpringBoot自动注入源码分析专车
前言 如果这是你第二次看到师长的文章,说明你在觊觎我的美色!O(∩_∩)O哈哈~ 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 本系列为SpringBoot深度源码专车系列,第一篇发车 ...
- springboot自动装配
Spring Boot自动配置原理 springboot自动装配 springboot配置文件 Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置.难以集成的内容(大多数流行第 ...
- 一步步从Spring Framework装配掌握SpringBoot自动装配
目录 Spring Framework模式注解 Spring Framework@Enable模块装配 Spring Framework条件装配 SpringBoot 自动装配 本章总结 Spring ...
随机推荐
- HashMap之tableSizeFor方法图解
目录 普通人的简单粗暴方式 示例代码 问题 大神的实现 移位的思想 全过程示意图 初始值 右移一位+或运算 右移二位+或运算 右移四位+或运算 右移八位+或运算 右移十六位+或运算 结果+1 初始容量 ...
- [DP浅析]线性DP初步 - 2 - 单调队列优化
目录 #0.0 前置知识 #1.0 简单介绍 #1.1 本质 & 适用范围 #1.2 适用方程 & 条件 #2.0 例题讲解 #2.1 P3572 [POI2014]PTA-Littl ...
- BIMFACE二次开发【C#系列】
本系列文章主要介绍使用 C# .ASP.NET(MVC)技术对 BIMFACE 平台进行二次开发,以满足本公司针对建筑行业施工图审查系统的业务需求,例如图纸模型(PDF 文件.二维 CAD 模型.三维 ...
- 翻译:《实用的Python编程》05_02_Classes_encapsulation
目录 | 上一节 (5.1 再谈字典) | 下一节 (6 生成器) 5.2 类和封装 创建类时,通常会尝试将类的内部细节进行封装.本节介绍 Python 编程中有关封装的习惯用法(包括私有变量和私有属 ...
- python-类的隐藏和封装
7 """ 8 封装是面对对象的三大特征之一(另外两个是集成和多态),它指的是将对象> 的信息隐藏在对象的内部,不允许外部程序直接访问对象内部信息,而是通> ...
- 平方十位数(蓝桥杯第八届国赛真题 JAVA-B组)
思路:从大到小枚举,判断其平方是否不重复 答案:9814072356 //水题 标题:平方十位数 由0~9这10个数字不重复.不遗漏,可以组成很多10位数字. 这其中也有很多恰好是平方数(是某个数的平 ...
- (4)MySQL进阶篇SQL优化(常用SQL的优化)
1.概述 前面我们介绍了MySQL中怎么样通过索引来优化查询.日常开发中,除了使用查询外,我们还会使用一些其他的常用SQL,比如 INSERT.GROUP BY等.对于这些SQL语句,我们该怎么样进行 ...
- IT培训有哪些坑(二)?
今天继续给大家分享一下IT培训都有哪些坑?有哪些不靠谱? 做招转的不靠谱.什么是招转?就是招聘转招生,名义上说的是招聘,但实际上做的就是招生.有很多大学刚毕业的计算机相关专业的同学,他们大学毕业之后, ...
- Istio 故障注入之延时(fixedDelay)
Istio 故障注入 Istio 故障注入与其他在网络层引入错误(例如延迟数据包或者直接杀死 Pod)的机制不同,Istio 允许在应用程序层注入故障.这使得可以注入更多相关的故障,比如 HTTP 错 ...
- 更改当前目录--cd
pwd 显示当前所在的目录路径 cd ../ 回到上一层目录 cd ../../ 回到上上层目录 cd / 回到根目录 cd ~ 回到当前用户的根目录 c ...