面试官:你说说Springboot的启动过程吧(5万字分析启动过程)

文章目录

前言

写完前言后,停顿了几千毫秒,忽然不知道说点啥了,写这篇博客的初衷的确就是面试官曾经问我的面试题,我支支吾吾,不知所云,总之就是,我没看过这个源码,这不,我就来补课了。工作确实比较忙,但这不是我不学习的借口,总结就是我发现我的固有思维还是缺少刨根问底的精神,以后我要养成习惯,凡事多问为什么,谨记!

一、Springboot是什么

我决定刨根问底,去官网逛哒逛哒~
个人认为:学习一门技术,在有点了解的情况想要准确的进一步了解的最好方式就是官网,如果一点都不了解也可以先看看别人写的,毕竟先看一些把官网的话翻译成小白容易理解的话,可以消除我们学习新技术的恐惧(为啥要恐惧,因为怕自己看不懂,觉得自己好笨,就失去信心了)。
话说springboot也出生很久了,我真的是没有踏踏实实的去学习,而是很多东西都停留在一知半解的,浅尝辄止的地方,这个习惯以后要改改了。谨记!

官网:https://spring.io/projects/spring-boot
另外也可从https://spring.io里的Projects里面进去:

在这里插入图片描述

在这里插入图片描述
没有中文版本的介绍,所以翻译一下:
OVERVIEW概览
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

Spring Boot 可以轻松创建独立的、生产级的基于 Spring 的应用程序,您可以“一键运行”。

We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

我们对 Spring 平台和第三方库持坚定的观点,因此您可以毫不费力地开始使用。大多数 Spring Boot应用程序需要最少的 Spring 配置。

If you’re looking for information about a specific version, or instructions about how to upgrade from an earlier release, check out the project release notes section on our wiki.

如果您正在寻找有关特定版本的信息,或有关如何从早期版本升级的说明,请查看我们 wiki 上的项目发行说明部分。

Features特点
Create stand-alone Spring applications

创建一个独立的Spring应用。

Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

直接嵌入Tomcat,Jetty 活Undertow(无需部署war文件)

Provide opinionated ‘starter’ dependencies to simplify your build configuration

提供“starter”依赖来简化构建配置

Automatically configure Spring and 3rd party libraries whenever possible

尽可能自动配置 Spring 和第三方库

Provide production-ready features such as metrics, health checks, and externalized configuration

提供生产就绪功能,如指标、健康检测和外部化配置

Absolutely no code generation and no requirement for XML configuration

绝对无需生成代码,也无需 XML 配置。

翻译的过程也是学习英语的过程,里面有些单词我确实不知道怎么翻译,借助了字典。
这里顺道学习一下英语,虽然跑题了,但学习的路上并没有跑题。

stand-alone 独立的
production-grade 生产级的
just run 一键运行(这里意译)
take an opinionated view of something 对一些东西持有坚定不移的观点
you can get started with minimum fuss 你可以毫不费力的开始使用
3rd party libraries 三方依赖库
whenever possible 尽可能
production-ready features 生产就绪功能

总结:SpringBoot可以快速的构建一个Spring项目,帮助开发者更专注业务逻辑的开发。

二、启动流程

2.1 构建Spring Boot项目

创建过程我就不写了,本来想自己重新创建一个Spring boot,但是我重装了系统之后,只能先装社区版本的idea,没有快捷创建的方式,但,但springboot官网提供了方式(https://start.spring.io),创建之后导入到idea就行。
在这里插入图片描述
画红框的地方就是我改动的地方,写完之后点击底部的GENERATE,创建完成之后就会自动下载了。
使用idea打开后,修改maven仓库地址后,等他下载看依赖,结构如下:
在这里插入图片描述
当然还有一些没有用的文件,可删可留,这不是我本次的重点。

2.2 启动的日志

直接run启动打印的日志如下:
在这里插入图片描述
看到这个熟悉的日志,自己以前从来没有想过:
第一行的jdk版本是在哪里被打印的?
spring的这个logo是在哪里打印的?
初始化Tomcat是在哪里加载的?
开始Servlet引擎是在哪里加载的?
web应用上下文是在哪里加载的?
最后启动成功是在哪里加载的?
。。。。。。
等等,下面就开启我们的寻宝之旅,一一揭秘!!!

2.3 启动流程

看完之后我感觉分成2部分:SpringApplication的构造函数和run方法的运行。

分析说明

  • 真是不看不知道,一看下一跳,之前没想过自以为只是一个简单的启动,实际上里面涉及的内容太多太多,而我看到的启动main方法真的只是冰山一角。本文写完5.3万字吧(当然里面有很多是方法名占了字数的名额),所以分析过程很啰嗦,涉及了很多的细节实现(当然还有很多没说到的),主要是因为自己是第一次分析Springboot源码,分析的时候想要把这些过程弄清楚并记录下来,方便自己整理里面的知识体系。文章篇幅较长,毕竟是自己一个多月边看边写完才完成这篇博客,所以看的时候可以分几次看,如果觉得啰嗦的可以直接看标题跳到自己想看的部分。
  • 当然,本着学习而非面试的目的,如果时间充足且有足够的兴趣还是强烈建议自己多跟几遍代码的执行过程,因为眼睛懂了,手却不动,最终还是会如过眼云烟。我们看源码的目的我感觉可以在了解其中的思想和优秀的写法,后期运用到自己的项目中,更好的为业务服务。
  • 另外,本文的分析是依据源码和对应的注释进行的分析,大部分的实现我还是未太过深究原因,自己后面有时间也会再多看看并深究如此设计的原因,当然我看的过程也写了不懂的地方,知道的同学还望不吝赐教哦~
  • 本文的食用方法:整个启动过程确实很多很多,分多次食用;由于很多地方都是承上启下,我也在文中有提到过某某代码在哪节分析过,忘记的可以回看,这个时候可以直接Ctrl+F进行搜索就好啦。

2.3.1 第一部分:SpringApplication的构造函数

Springboot的启动入口:SpringBootDemoApplication.main方法中的run方法:
在这里插入图片描述
点击mian中的run方法进入SpringApplication类的run方法
在这里插入图片描述
这里在堆中开辟了一个空间,存放创建的SpringApplication的实例对象,将放有SpringBootDemoApplication.class的数组作为SpringApplication的构造函数的入参,而main方法中的args作为run方法的入参。接着我们进入SpringApplication的构造函数。
通过debug启动看一下里面加载之后各个属性的值是什么,如下图一:
在这里插入图片描述
我们重点看一下里面的几个属性:

A、webApplicationType(web应用类型)

进入WebApplicationType.deduceFromClasspath()方法可以看到
在这里插入图片描述
通过Debug可以看到,这里是直接走的最后一行:return WebApplicationType.SERVLET,也就是说webApplicationType=SERVLET,如上图一;

B、引导注册初始化器

这个名字我不知道怎么说,但看到最后2个单词,应该是注册初始化器,后面的两个setXX方法也有加载初始化器,可以看到都调用了一个共同的方法getSpringFactoriesInstances,只是入参不一样。
在这里插入图片描述
进入getSpringFactoriesInstances方法,最后到SpringFactoriesLoader.loadFactoryNames方法调用了SpringFactoriesLoader.loadSpringFactories方法,在这个方法中有一行
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
通过debug可以看到,加载的URL就是maven包里的springframework下面的
1)spring-beans/5.3.23/spring-beans-5.3.23.jar!/META-INF/spring.factories
2)spring-boot-devtools/2.7.5/spring-boot-devtools-2.7.5.jar!/META-INF/spring.factories
3)spring-boot/2.7.5/spring-boot-2.7.5.jar!/META-INF/spring.factories
4)spring-boot-autoconfigure/2.7.5/spring-boot-autoconfigure-2.7.5.jar!/META-INF/spring.factories
其实应该是加载了所有包下的META-INF/spring.factories文件,只要有存在key=BootStrapRegistryInitializer的类都会被加载进来。
下面我用debug展示一下3和4包加载的内容
在这里插入图片描述
截图中的properties里有14个,这些属性就是读取的文件里的,但这个文件里远远不止于14个,仔细一看是取了14种的第一个放到Properties里面了,不信小伙伴也可以数一下
在这里插入图片描述
以及spring-boot-autoconfigure/2.7.5/spring-boot-autoconfigure-2.7.5.jar!/META-INF/spring.factories
在这里插入图片描述
将4个jar包中的spring.factories文件的key-value都加载后放到result中后,就到了替换map中的工厂实现名(factoryImplementationName这玩意我就直译了),result的结构如下:
Map<String, List<String>> result = new HashMap();
在这里插入图片描述
接着执行方法内部的lambda表达式,将装在ArrayList中的监听器或初始化器等放到Collections$UnmodifiableRandomAccessList容器里。
在这里插入图片描述
替换的时候发现为什么implementations的长度是31个呢,我数了一下spring-boot和spring-boot-autoconfigure的META-INF/spring.factories文件里的FailureAnalyzer的实现类,加起来就是31个。

还看了其他的发现不够数,在debug的过程中可以看到也在加载其他的springframework依赖中的,比如
org.springframework.context.ApplicationContextInitializer中有8个,我发现在spring-boot和spring-boot-autoconfigure的META-INF/spring.factories文件只找到了7个,还有org.springframework.boot.devtools.restart.RestartScopeInitializer没有找到,于是记得加载过spring-boot-devtools/2.7.5/spring-boot-devtools-2.7.5.jar!/META-INF/spring.factories,在这里果然找到了:
在这里插入图片描述
替换走完之后看到debug控制台显示的Map结果:
在这里插入图片描述
替换前后对比发现:
在这里插入图片描述
仅仅是换了一下,这里为啥要替换呢,我没有看的太明白,我们先接着往下看就到了调用此方法的SpringFactoriesLoader.loadFactoryNames方法:
在这里插入图片描述
我们翻看4个jar包中/META-INF/spring.factories都没有找到BootstrapRegistryInitializer,所以SpringApplication的构造器的属性this.bootstrapRegistryInitializers是一个空的ArralyList。

C、设置初始化器

我们重新回到SpringApplication的构造器看下初始化器和监听器的方法:

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

又看到了熟悉的方法getSpringFactoriesInstances也就是上面重点分析读取的4个jar包下的/META-INF/spring.factories的内容,我们发现result的map中是有ApplicationContextInitializer的,而且它有8个孩子,这个前面也分析过,这里不赘述,截图展示一下debug的内容:
在这里插入图片描述

D、设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

ApplicationListener也是有10个孩子的,具体在哪里jar包里,我就不再找一遍了。
在这里插入图片描述

E、this.mainApplicationClass(设置应用程序类)

最后一步推断主要的应用程序类
在这里插入图片描述
到这里总算把构造函数的执行过程分析完了。真的是不分析不知道,一分析吓一跳。当然这只是我自己的分析,里面还有2点我没有搞明白,还需要再多debug几遍,估计才能吃透,大家要想真的搞懂了,很久不会忘,也要自己动手debug几遍,这样才能内化成自己的知识。
另外,这里面用到了一种机制,也是我之前写过的一篇博客说到的Dubbo的SPI机制,java中是最先使用的,Dubbo在java的SPI机制的基础上做了优化。

2.3.2 第二部分:run方法的运行

先看一下这个方法内都写了些啥
在这里插入图片描述
为了便于分析和理解,我这里以打印的banner信息为分界点进行分析,看一下前后都做些了啥。

A、打印banner前的代码实现

//1、获取系统纳秒数时间,用于统计执行的总时间
long startTime = System.nanoTime();
//2、创建启动的上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//3、配置无领导的属性
configureHeadlessProperty();
//4、获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//5、发布启动的事件并由对应的监听器进行处理
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
    
    
	ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	//6、准备环境变量
	ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
	//7、配置忽略的Bean信息
	configureIgnoreBeanInfo(environment);
	//8、打印Banner信息
	Banner printedBanner = printBanner(environment);

我先把大致的过程在注释里写一下,下面针对除1以外的其他操作说一下。

A.2、创建启动的上下文

在这里插入图片描述
这里对bootstrapRegistryInitializers进行了遍历,很显然,这个里面是没有数据的,这里在2.3.1.B有详细说过,不再赘述。最终启动的上下文是DefaultBootstrapContext,这个的作用是在第5步的时候用到了。

A.3、配置无领导的属性

其实这个我没有太懂,设置这个的目的是什么,但还是硬着头皮往下看了
在这里插入图片描述
从System中获取java.awt.headless的属性值,把结果再放进去,貌似是这样,没有看太明白,有知道的小伙伴看到这里也可解惑一二哈~
Properties.getProperty方法里,返回了true。
在这里插入图片描述
我好奇这个java.awt.headless是什么,看着像路径名,于是我找到了awt包,在这个包里只看到了HeadlessException类:
在这里插入图片描述
类的上面有一句注释:

在不支持键盘、显示器或鼠标的环境中调用依赖于键盘、显示器或鼠标的代码时引发。

那把这个值设置为true的目的是什么呢,我从一篇文章中看到正确的解释:
SpringBoot其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置。
感谢这位大神的解释,醍醐灌顶的赶脚~

A.4、获取运行的监听器
//4、获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);

又是熟悉的配方,又是熟悉的味道,getSpringFactoriesInstances方法又来串门了。
在这里插入图片描述
我们很容易就在spring-boot/2.7.5/spring-boot-2.7.5.jar!/META-INF/spring.factories里面找到SpringApplicationRunListener的子类org.springframework.boot.context.event.EventPublishingRunListener
在这里插入图片描述
所以getSpringFactoriesInstances方法返回的监听器也是EventPublishingRunListener,但getRunListeners方法里面return了,返回了一个创建的新的SpringApplicationRunListeners,构造了此类,将监听器放在构造器了。
在这里插入图片描述

A.5、发布启动的事件并由应用程序启动监听器进行处理

接着我们往下走,调用了SpringApplicationRunListeners.starting方法。
在这里插入图片描述
进入starting方法又调用了doWithListeners方法,此方法中第一个参数是步骤名,第二个是一个函数式接口,第三个是步骤,其中最重要的就是函数式接口。
在这里插入图片描述
进入doWithListeners方法,方法体中的this.applicationStartup.start(stepName);最终返回了一个DefaultStartupStep
doWithListeners方法在SpringApplicationRunListeners类中会被反复的使用,后面也会提到。
在这里插入图片描述
后面有调用了this.listeners.forEach(listenerAction);方法,那么这里listenerAction是来源于doWithListeners方法里的(listener) -> listener.starting(bootstrapContext)方法返回的结果,此方法才是核心的方法。由于内功修炼不够,这个方法起初我没有看懂,于是反复debug了5,6遍才看懂,没有看懂的点是Listener的集合中有10个元素,为啥最后过滤完之后只剩下了下图中我划线的序号为0,3,4,5的4个,剩下的6个为啥被过滤掉了。
在这里插入图片描述
我把这四个类的创建信息找到了
在这里插入图片描述
而其他的类的创建信息如下:
在这里插入图片描述
序号为7的DevToolsLogFactory我就不贴了,对应的监听类是在此类的内部,就不考虑了。
比较关键的方法是starting方法,在SpringApplicationRunListener类中,此方法上有一行注释,翻一下:

首次启动 run 方法时立即调用,可用于提前初始化。

在这里插入图片描述点击multicastEvent进去后,最重要的应该是getApplicationListeners(event, type)方法了,此方法就是返回了前面说的那4个监听类,在使用invokeListener方法调用doInvokeListener方法,这个方法我们最后再看。
记住这个multicastEvent方法,后面还有几处会使用此方法
在这里插入图片描述
这里getApplicationListeners(event, type)方法很重要,最终调用了retrieveApplicationListeners方法,此方法中有一个循环中的supportsEvent(listener, eventType, sourceType)方法就是过滤出来这4个监听器的方法。
在这里插入图片描述
supportsEvent(listener, eventType, sourceType)返回true时,就会把listener添加到集合中,此方法使用了instanceof关键字来判断前面的对象是否属于后面的类,或者属于其子类。这个方法是什么意思呢?方法上有一行注释:

确定给定监听器器是否支持给定事件

在这里插入图片描述
可以看到,4个类中只有LoggingApplicationListener是实现了GenericApplicationListener其他的监听器需要使用GenericApplicationListenerAdapter.supportsEventType(eventType)来进行判断。
其中最后一行是一个&&连接的,后一个直接返回了True,就看前一个方法smartListener.supportsEventType(eventType)了,此方法进去后就到了GenericApplicationListenerAdapter类,此类实现了GenericApplicationListener,其中有个构造方法:
在这里插入图片描述
此类重写了方法supportsEventType方法
在这里插入图片描述
10个类中大部分都是直接走的最后一个else,也就是“||”后面的方法,this.declaredEventType.isAssignableFrom(eventType)),在这个方法中有个很关键的地方来决定最终过滤掉10个类中的哪些类,代码有点多,我就不贴太多了,感兴趣的要自己debug再深入了解。
isAssignableFrom方法的2/3处,大部分的类都因为ClassUtils.isAssignable(ourResolved, otherResolved)返回了false的被过滤掉。这个方法的说明为:检查右侧类型是否可以分配给左侧类型。
在这里插入图片描述
(说明:此方法我大概理解为这10个类创建的时候实现的类的泛型是否为ApplicationEvent类或其子类,没有看的太明白,我这里只是把自己看到的说明一下,至于实现的思想,可能还需要我内功在深厚些才能看懂,另外,获取监听器的步骤也是我看的时间最久,不理解的点最多的地方了,所以就先到这里,当然再往下走还有需要深挖的地方,感兴趣的小伙伴可以自行深挖)
最后的最后,我们回到invokeListener方法来看一下调用监听器的方法invokeListener又调用doInvokeListener:
在这里插入图片描述
这四个类都各自重新了实现了onApplicationEvent方法,当然重写此方法的类有37个,我就不截图列举了,不信,不信可以自己找找哈~不同的实现类接收到事件之后,就根据事件的数据进行个字的处理。
根据上面说的multicastEvent进去后,getApplicationListeners(event, type)返回来的监听器,进入到对应的监听器的实现方法中,比如这里的starting方法,传的事件类是ApplicationStartingEvent,那么哪个监听类来处理这个事件呢,我们全局搜一下,或者点击onApplicationEvent方法的实现中的我们上面说的那四个中打上断点,看到进去了哪个就知道是哪个监听器处理这个事件了。两种方法我都试了,都挺好使的。我们找到了监听器来RestartApplicationListener来处理ApplicationStartingEvent事件,如下图,图的名字为启动事件代码图
在这里插入图片描述
如果这个事件处理完了之后,就会在控制台打印一行日志,即下图中画红线的这行
在这里插入图片描述
那么这行日志是在哪里打印的呢?我就简单粗暴的展示下调用路径吧,自行查看,或全局搜索日志关键字Created RestartClassLoader后倒推也行。从启动事件代码图中画蓝色阴影这行开始:
onApplicationStartingEvent((ApplicationStartingEvent) event);->Restarter.initialize(args, false, restartInitializer, restartOnInitialize);->localInstance.initialize(restartOnInitialize);->immediateRestart();->start(FailureHandler.NONE);->Throwable error = doStart();->ClassLoader classLoader = new RestartClassLoader(this.applicationClassLoader, urls, updatedFiles);
就是在RestartClassLoader的构造函数中打印的
(偷偷告诉你:RestartApplicationListener里面接了好几个事件有没有发现,下面我们还会遇到的哦~)

A.6、准备环境变量

说完了我自认为最难的地方了,就感觉轻松很多了。
我们现在来到了这里
在这里插入图片描述
prepareEnvironment 方法如下:

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    
    
		// 1、获取或创建环境变量
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		//2、配置环境变量
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		//3、配置属性源和环境变量的依附
		ConfigurationPropertySources.attach(environment);
		//4、监听环境变量
		listeners.environmentPrepared(bootstrapContext, environment);
		//5、将默认的环境变量移动到最后
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		//6、将环境绑定到SpringApplication
		bindToSpringApplication(environment);
		//7、转换环境为标准的环境变量
		if (!this.isCustomEnvironment) {
    
    
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		//8、配置属性源和环境变量的依附(同第三步,不赘述了)
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
A.6.1 获取或创建环境变量

在这里插入图片描述
这里创造了一个ApplicationServletEnvironment应用服务环境变量类,此类继承自StandardServletEnvironment,此类又继承StandardEnvironment,此类又继承AbstractEnvironment,这个类是抽象的环境变量类,在这个抽象的环境变量类中,有个构造方法:
在这里插入图片描述
进入到customizePropertySources(propertySources);方法中,
在这里插入图片描述
接着在super.customizePropertySources(propertySources);方法中又添加了2个元素,添加完之后,environment如下:
在这里插入图片描述
那么propertySourceList的四个元素中的source也是List,在类public class MutablePropertySources implements PropertySources中有propertySourceList的定义private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();此集合是一个写时复制的集合类。
我们看一下inde为2和3的soure里有什么,这里以index=2举例,存的是系统属性,从下图中可以看到,source里面存了系统的基础信息,虚拟机版本,class版本,运行环境,项目目录等等。
在这里插入图片描述3的话存的是系统环境变量,里面的key都是大写的,因为我们平时配置环境变量也确实是大写的。
在这里插入图片描述
说明:关于获取系统属性(getSystemProperties())和系统环境变量(getSystemEnvironment())的方法我这里就不展开说了,不然感觉没完没了了。

A.6.2 配置环境变量

接着进入了配置环境变量的方法中:configureEnvironment(environment, applicationArguments.getSourceArgs());
在这里插入图片描述
接着进入 configurePropertySources(environment, args);方法,判断都不通过,直接return。
在这里插入图片描述

A.6.3 配置属性源和环境变量的依附

再往下走,就是将属性源和环境变量进行匹配的方法:ConfigurationPropertySources.attach(environment);执行完此方法,propertySourceList集合里面又多一个家庭成员:
在这里插入图片描述
当你按照下面的几个步骤操作:

  • 打开propertySourceList集合中的index=0的元素;
  • 打开source;
  • 再打开source里面的sources

按照上面的三步不断重复,如此重复十几次,就像无限套娃一样,没完没了,真的是好像没有穷尽,如下图
在这里插入图片描述
那这个效果是如何实现的,又为啥这么设计呢?说实话,如此设计的原因我还真的不知道,但实现这种效果的过程我们可以看下,进入ConfigurationPropertySources.attach(environment);方法,这是一个静态方法。
在这里插入图片描述
这里的操作是把前面集合中的4个属性源元素打包作为一个整体放到了给了新创建的对象ConfigurationPropertySourcesPropertySource里面,接着最后一行又放到了原来的sources里面的第一个元素位置上,也就是我们前面看到的无限套娃的效果。
在这里插入图片描述
如果按照前面说的那三步不断展开,也是一样的无穷尽的效果。如果有小伙伴能看到这里,并知道为啥要这样无限套娃还请留言指教哦~

A.6.4 监听环境准备

接着来到了listeners.environmentPrepared(bootstrapContext, environment);方法,这里复用了咱们上面分析获取监听器的方法:
在这里插入图片描述
调用了监听器里面重写的multicastEvent方法发布了环境准备的事件
只不过是监听的操作
那接收此事件的监听器都做了什么操作呢?接收这个事件的监听器是EnvironmentPostProcessorApplicationListener,而此类的无参构造器中又做了一件我们很熟悉的事情:
在这里插入图片描述
看到SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));是不是就知道了,我们之前有分析过,就是从spring.factories中加载EnvironmentPostProcessor的子类,加载下来就是下图中这些:
在这里插入图片描述
上图中画红线的方法就是将从spring.factories中获取的字符串的类转为真的类,对每个类进行遍历,这些类重写了postProcessEnvironment方法,各自进行环境的处理,当遍历到RandomValuePropertySourceEnvironmentPostProcessor时:
在这里插入图片描述
此方法只有一行代码,调用了一个静态方法:addToEnvironment
在这里插入图片描述
在这个方法里,将name为random的资源类RandomValuePropertySource添加到MutablePropertySources里面,即propertySourceList集合中,当遍历到类DevToolsPropertyDefaultsPostProcessor后,也将属性资源放到了集合中:
在这里插入图片描述
至此,集合中有6个属性资源了:
在这里插入图片描述
添加devtools完成之后,propertySourceList集合中就有7个元素了:
在这里插入图片描述

A.6.5 将默认的环境变量移动到最后

此方法的位置如下图:
在这里插入图片描述
进入方法里面,最终返回了false。
在这里插入图片描述

A.6.6 将环境绑定到SpringApplication

在这里插入图片描述
图中标记1和2的地方是我比较困惑的地方,标记为1的我没有看太懂,断言了个啥,所以这行就放过了。
再来看标记为2的地方,我在这个方法里debug了不下十次,一路进入方法内容,我不知道是在哪里做到的一直循环,因为我没有找到循环体,主要循环的地方就是下图中的bind方法了。如果有知道的大神可以指教一二,我想知道我是卡在了哪里?
在这里插入图片描述
这里的ConfigurationPropertyName都是从哪里来的,在不断的循环过程中,我发现有些key就是下图中的Json文件里的name,但我不知道这个文件是在什么时候被读取的。
在这里插入图片描述
等我悟了再来补充了。

A.6.7 转换环境为标准的环境变量

在这里插入图片描述
接着又执行了第三步,配置属性源和环境变量的依附(ConfigurationPropertySources.attach(environment);)
我依然没有明白为啥要再执行一遍,最后就把处理好的environment返回了。

PS:里面确实有很多,我还是没有看懂,不过没有关系,可以先知道大概的逻辑,后面再慢慢看看。说不定就豁然开朗了。

A.7、配置忽略的Bean信息

在这里插入图片描述
从这个方法的实现可以看出来,他是配置忽略BeanInfo,先从System中的属性中看是否能获取到spring.beaninfo.ignore的值,获取不到就从环境变量中拿,并将在System中的此值设置为True,这里也没有看懂为啥要这么做,设置为true的目的是什么,为什么要忽略BeanInfo,我想后面应该要用到的吧。

A.8、打印Banner信息

在SpringApplication中的printBanner方法中,bannerMode默认为CONSOLE,在此方法内进入到SpringApplicationBannerPrinter方法的print方法,接着调用了SpringBootBanner方法的printBanner方法。
在这里插入图片描述
SpringBootBanner类中:
在这里插入图片描述
当然也可以自定义图片或文本,将自定义的图片或文本放到resources文件夹下,从下面这段代码可以看出来是支持自定义扩展的:
在这里插入图片描述
至此,run方法中的第一部分就分析完了。可真是要了我的老命了,也花了很多时间,依旧有些不明所以。

B、打印banner后的代码实现

在这里插入图片描述
这里大体又是9个步骤,我们依旧一一来看下:

1、创建应用程序的上下文
2、准备上下文
3、更新上下文
4、更新上下文的后置处理
5、打印应用程序花了多久启动完成
6、发布上下文开始的事件
7、调用运行器
8、处理捕捉到的运行的异常
9、发布准备就绪的事件

B.1、创建应用程序的上下文

createApplicationContext();方法的注释是:

用于创建ApplicationContext的策略方法。默认情况下,此方法将遵循之前任何显式设置的应用程序上下文类或工厂回退到合适的默认值。

进入到此方法,调用的是ApplicationContextFactory的抽象的create方法,那该看哪个实现类的create方法呢?
在这里插入图片描述
我们进入到ApplicationContextFactory.DEFAULT里面,看到for循环里有个loadFactories方法
在这里插入图片描述
当你沿着SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())方法一路往下追,就可以看到又是查询的META-INF/spring.factories文件中的SPI,于是全文查一下ApplicationContextFactory就真的搜到了其接口实现,也就是我下图中红框的部分。
在这里插入图片描述
接着就知道去哪个实现类里去看创建的方法了。
在这里插入图片描述
所以,最后context的类型是AnnotationConfigServletWebServerApplicationContext
那么我们看一下这个类和create方法返回的类ConfigurableApplicationContext有什么样的关系吧
在这里插入图片描述
此图我们起个名字叫上下文关系继承图吧,从这张继承图可以看出来,画红框的最上面(ConfigurableApplicationContext)和最下面(AnnotationConfigServletWebServerApplicationContext)的关系,这个图我们下面一节准备上下文中有一个方法也要用,先过个眼熟,那这个AnnotationConfigServletWebServerApplicationContext里面有啥呢,我们看下构造函数:
在这里插入图片描述
而这个AnnotatedBeanDefinitionReader又是啥,我们再看下构造函数:
在这里插入图片描述
最后一行竟然有个注册的方法,这里注册了啥呢,我们先跳过这里不谈,后面会做进一步的分析。

B.2、准备上下文

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
先说下准备上下文的上一行的这个方法:context.setApplicationStartup(this.applicationStartup);
在这里插入图片描述
看一下 ApplicationStartup类的翻译后的中文注释:

使用StartupStep检测应用程序启动阶段。核心容器及其基础结构组件可以使用ApplicationStartup标记应用程序启动期间的步骤并收集有关执行上下文的数据或其处理时间。

点击setApplicationStartup方法进去后可以看注释:

允许应用程序上下文在运行期间记录指标。

再进去之后就是set属性的方法了, 这里不再赘述了。接着我们看一下关键的方法:
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
这个方法是启动的流程中参数最多的了。需要首先说明一下,这个方法和下面的更新上下文的方法中过程比较多,需要做好心理准备了。
这个方法我们分成几部分来看下,还是惯例,除非特别简单的不做分析,其他的会一一进行分析:

B.2.1处理上下文

postProcessApplicationContext(context);方法的中文释义:

此方法应用于任何相关的ApplicationContext的处理。子类可以根据需要进行额外的处理。

在这里插入图片描述
依旧是debug了好些遍,也没有搞明白这个方法我该怎么翻译,方法里面有3个if块,但只有最后if块中的setConversionService方法走了下去,我起先并不明白,为啥要set一个这样的东西,既然有set那肯定有get,get是被用在了哪里,conversionService是用来干啥的?
进入setConversionService方法看到个注释:

指定用于转换属性值的 Spring 3.0 转换服务,作为 JavaBeans 属性编辑器的替代方案。

注释上说这个从Spring3.0开始的,相当于升级了。
我们将context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());分解下,
先看context.getEnvironment().getConversionService())方法:
点击getConversionService方法,我们进入的是ConfigurablePropertyResolver类,在方法上有个注释和使用方法:
在这里插入图片描述
进入到这个方法的实现类AbstractPropertyResolver里:
在这里插入图片描述
最终返回了一个ConfigurableConversionService类,此时感觉各个类的关系有点复杂,我们看一下关系图就清晰了:
在这里插入图片描述
在上图中,我们是从ConfigurablePropertyResolver里面拿的ConfigurableConversionService,这个ConfigurableConversionService继承ConversionService,ConverterRegistry,这两个接口中,前者是处理转换(<T> T convert(@Nullable Object source, Class<T> targetType);),后者是转换器注册(void addConverter(Converter<?, ?> converter);方法)。

再来说一下context.getBeanFactory().setConversionService(ConversionService)方法,点击setConversionService进入AbstractBeanFactory里面,看到了set和get方法,那么get方法是在哪里调用的呢?
在这里插入图片描述
如下图,可以看到有哪些类调用了getConversionService方法:
在这里插入图片描述
从上图中第三个有个typeConverter的调用看出,和类型转换有关。我们在前面的类的关系图中看到,以及Conversion是转换的意思来看,这个ConversionService就是转换服务的顶级接口,而他下面有几个实现类:
在这里插入图片描述
我进去到StandardBeanExpressionResolver类了,从命名上可以看到,这个类是用来解析Bean的,这个类上的注释也有类似的说明:

包含 {@code BeanFactory} 的所有 bean 都作为预定义变量提供,并具有其通用 Bean 名称,包括标准上下文 bean,例如 “environment”、“systemProperties” 和 “systemEnvironment”。

我又接着往上追发现一些方法的命名上的意思大概是和Spring的xml配置的解析有关,前期使用Spring的时候我们一般使用的是xml配置,里面会写一些String字符串,那么这些字符串是如何转成对应的类的呢?我想是和这个接口有关的。看下图就明白了,包括了Integer,Double,Boolean等基本类型的转换,日期的转换等,每一个都有对应的接口实现:
在这里插入图片描述
其实里面可说可挖的东西还有,但限于篇幅有限,就点到为止吧,这里留个作业:使用ConversionService将String类型的数字转为Interger。


说完了conversionService,在来说说第一个if块中,这里判断beanNameGenerator不为空后就放入缓存中,那什么时候对进行set的呢?
SpringApplication中有个setBeanNameGenerator方法,这个方法被SpringApplicationBuilderbeanNameGenerator调用,但这个beanNameGenerator方法又是在哪里调用的就找不到了~
在这里插入图片描述
而下面的if块中的resourceLoader不为null,在进行下面的逻辑,那么这个是在哪里进行set的,发现在我们第一部分分析的构造函数中有,但这个resourceLoader确实默认为null,那就说明还有别的地方进行的set。
在这里插入图片描述
beanNameGenerator一样在SpringApplicationBuilder里面调用的:
在这里插入图片描述
SpringApplication中有个setResourceLoader方法,这个方法被SpringApplicationBuilderresourceLoader调用,但这个resourceLoader方法又是在哪里调用的,我死活找不到。
这里以后看懂了再来补充,当然,如果看到了这里,且知道为啥,还望留下足迹,为我解惑一二哦~

B.2.2 初始化器应用到上下文

applyInitializers(context);
此方法的中文注释:

在上下文之前将任何ApplicationContextInitializer应用于上下文刷新。

进入这个applyInitializers方法之后,就是一个for循环,那循环的内容是什么呢? getInitializers()返回的结果就是我们在2.3.1节分析过的C、设置初始化器的方法里的8个初始化器,而这8个初始化器从spring.factories文件中拿的,不记得了的话再回去看一下哈~
在这里插入图片描述
接着往下走,看到下图中画红框的地方
在这里插入图片描述
画红框中代码的目的是什么,在上一节B.2节中我们画过一个上下文关系继承图还记得否?而这个方法GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class)就是判断context是不是遍历到的初始化器实现的类ApplicationContextInitializer的泛型的实例,是的话,才调用遍历调用各个实现类的initialize实例化方法。当我们把这个方法走完之后,发现context.applicationListeners多了三个元素:
RSocketPortInfoApplicationContextInitializer$Listener
ServerPortInfoApplicationContextInitializer
ConditionEvaluationReportLoggingListener$ConditionEvaluationReportListener
在这里插入图片描述
为啥是这三个类放到应用监听器里了,其他的5个为啥没有放进去呢?我们进入到这三个类实现的initialize方法看一下:
在这里插入图片描述
其他的5个是没有调用方法applicationContext.addApplicationListener;的,所以就不会放到应用监听类的集合里了。

B.2.3 发布上下文准备的事件

listeners.contextPrepared(context);
现在一看到listeners调用什么方法就本能的反应是发布了个事件:
在这里插入图片描述
那发布的这个事件ApplicationContextInitializedEvent被哪个监听器接收后做了什么处理呢?很遗憾我没有找到接收这个事件的监听器,兴许还有别的方式来处理这个事件?有小伙伴知道的话不要不舍得赐教哦~

B.2.4 发布引导程序关闭的事件

bootstrapContext.close(context);
此方法对应的中文注释:

BootstrapContext 关闭并且ApplicationContext已准备好会调用此方法。

在这里插入图片描述
很显然这里又是发布了一个事件BootstrapContextClosedEvent,和上一节发布上下文准备的事件一样,还是没有找到对应的监听器来处理这个事件。

B.2.5 打印正在启动的日志

在这里插入图片描述
这两行执行完就打印了下面红框的两行日志,说明正在启动应用,应用名称,使用的jdk版本,进程id,项目目录
在这里插入图片描述
具体的实现就是在下面的这个方法里实现的,比较简单,就是字符串的追加和属性的获取,这里就唠叨了。
在这里插入图片描述

B.2.6 Bean工厂的设置

这个标题听着有点像冰工厂,我将Bean工厂设置分成了三个部分:

1)应用参数单例Bean和Spring标志单例Bean添加到缓存中
2)是否允许循环依赖和是否允许Bean定义被覆盖
3)添加Bean工厂处理器集合到上下文中
在这里插入图片描述

1)应用参数单例Bean和Spring标志单例Bean添加到缓存中
我们之间点击registerSingleton方法,进入了DefaultListableBeanFactory.registerSingleton方法,此方法调用了super.registerSingleton(beanName, singletonObject);的方法,也就是DefaultSingletonBeanRegistry.registerSingleton(beanName, singletonObject)方法。
下面的这张图我们命名为添加单例bean代码图吧,下面还要用这张图
在这里插入图片描述
走到了这里有没有发现什么问题?明明我们才添加了两个bean,为啥就有4个了呢,另外两个哪里来的?这时候需要搭乘时光穿梭机,让我们把时光倒退到B.2.2 初始化器应用到上下文中分析过applyInitializers(context);方法,就是8个初始化器进行遍历和和初始化,而beanFactory中就有其中2个初始化器工作后的结果。
趁着上图中的热,想说说心里话,这里四个缓存除了registeredSingletons以外的其他三个不就是Spring的三级缓存嘛,这里这么设计的目的我们姑且先不议,我们先看一下这四个集合的定义:
在这里插入图片描述
之所以这么设计肯定有原因,别急,继续往下看。
不过我想先插播一条广告,关于两个类:DefaultSingletonBeanRegistryDefaultListableBeanFactory的关系图:
在这里插入图片描述
因为我前面疑惑为啥在DefaultSingletonBeanRegistry点击super.registerSingleton(beanName, singletonObject);就直接进入 DefaultListableBeanFactoryregisterSingleton方法了,一看这继承关系真的是老长了~

广告结束,回到正题,我们的时光机回到了B.2.2 初始化器应用到上下文中的两个初始化器:ContextIdApplicationContextInitializerConditionEvaluationReportLoggingListener

  • ContextIdApplicationContextInitializer
    这个初始化器是为了设置应用上下文的ID的,在此类的注释上进行了说明,直接debug进入初始化方法,是不是看到了熟悉的registerSingleton方法,进去后就是我们在前面的添加单例bean代码图的流程了,添加到2个缓存中,不啰嗦了~~
    在这里插入图片描述
  • ConditionEvaluationReportLoggingListener
    又是老朋友了,我们前面分析过应用上下文中的3个监听器中就有它,注释说这个初始化器的目的是编写报告到日志,且是一个不会共享实例的bean,即单例bean。
    再来说一下这个值:autoConfigurationReport是怎么来的?随着for循环,我们来到了ConditionEvaluationReportLoggingListener类里,if块中有个get方法:
    在这里插入图片描述
    从get方法进去后,我们揭开了autoConfigurationReport的面纱。
    在这里插入图片描述
    走到这里就知道下面要干啥了,没错,就是前面的添加单例bean代码图的同样的逻辑,不啰嗦了。
    2)是否允许循环依赖和是否允许Bean定义被覆盖
    在这里插入图片描述
    感觉这里没啥可以说的,两个if块都可以进去,且是否循环依赖和是否允许Bean定义被覆盖都是默认的false。

3)添加Bean工厂处理器集合到上下文中
在这里插入图片描述
addBeanFactoryPostProcessor方法上有行注释:

添加新的 BeanFactoryPostProcessor,在刷新之前,该处理器将应用于此应用程序上下文的内部 Bean 工厂,在任何对 Bean 定义进行评估。在上下文配置期间调用。

在这里插入图片描述
其实就是把装有context的BeanFactoryPostProcessor又放到了都是BeanFactoryPostProcessor的List集合中,集合定义上也说了这个是用在更新上下文上的,也就是我们后面B.3节更新上下文要分析的方法,那我们就留到后面再说不迟。
视线再拉回来,如果lazyInitialization的值为true,添加的就是没有上下文的BeanFactoryPostProcessor放到了集合中,默认lazyInitialization这个值为false,所以不走if块里面。
当我们执行完这一步之后,发现beanFactoryPostProcessors里面有三个元素,那其中2个是什么时候添加进去的呢?
在这里插入图片描述
和第一步一样都是在B.2.2 初始化器应用到上下文,即applyInitializers(context);方法中对应初始化器的initialize方法添加进去的:

  • SharedMetadataReaderFactoryContextInitializer
    在这里插入图片描述
  • ConfigurationWarningsApplicationContextInitializer
    在这里插入图片描述

至此,Bean工厂的壮大的历程就分析完了。

B.2.7 加载资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));

首先看getAllSources方法,进去后,此方法上有行注释:

返回一组不可变的所有源,这些源将在调用 {@link #run(String…)} 时添加到 ApplicationContext。此方法将构造函数中指定的任何主要源与已显式设置@link #setSources(Set) } 的任何其他源组合在一起。

在这里插入图片描述
我们在分析SpringApplication的构造函数的时候,有说到这个primarySources就是放有自定义的启动类class com.fanhf.demo.SpringBootDemoApplication的set集合,现在把这个集合的元素放到了新的不可修改的Set集合中再放到ApplicationContext中。

接着我们看下load方法,先走完断点看看里面的结果
在这里插入图片描述
里面又是多个if块,其中画红线的方法就是构造BeanDefinitionLoader,而里面的
getBeanDefinitionRegistry(context)则是为了返回Bean定义的注册器,如下图:
在这里插入图片描述debug走到了第一个if块中,context为啥是BeanDefinitionRegistry的实例呢,还记得我们在B.1节的一个上下文关系继承图吗?AnnotationConfigServletWebServerApplicationContextBeanDefinitionRegistry的一个子类,所以说结果为True;
createBeanDefinitionLoader构造了BeanDefinitionLoader,以便执行loader.load();方法。
在这里插入图片描述
这里还需要补充一句,这个构造器中第4行中有行代码:this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);,我们下面还会提到这个构造器方法!!我们给这个上面这个图起个名字吧:就叫BeanDefinitionLoader构造函数图
最后来看下loader.load();方法:
在这里插入图片描述
从这里我们进入了load方法:
在这里插入图片描述
第二个if块中有个isEligible判断,意思是检查Bean是否符合注册条件,这个结果是True,细节就不说了,接着就是注册了,这个annotatedReader就是AnnotatedBeanDefinitionReader好像在哪里见过~~别急,待会我们再来说道说道吧。这个register方法走完,load方法也就结束了。

一路来到了doRegisterBean方法,我们之间关注最后一行的注册方法:
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
再到registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());里面后,有一行BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);,而beanName=“springBootDemoApplication”,很显然Map中是没有的,existingDefinition=false,就来到了else的else块中,即下图,我们给这个图起个名字吧,下面要用,就叫Bean注入图
在这里插入图片描述
最开始好奇的是,beanDefinitionNames怎么就已经有5个了,这五个是从哪里来的,所以又得开着时光机往回看了,这就得说回到AnnotatedBeanDefinitionReader了。我们在BeanDefinitionLoader构造函数图B.1、创建应用程序的上下文上下文关系继承图下面有提到过这个类的构造函数中有个注册的方法,而这5个Bean的注册入口就是在AnnotationConfigUtils.registerAnnotationConfigProcessors方法。我这里就罗列几个:
在这里插入图片描述
画红线处的registerPostProcessor方法参数中最后一个参数就是beanName的常量值。拿第一个bean来举例进入到registerPostProcessor方法:
在这里插入图片描述
此时调用的了registry.registerBeanDefinition(beanName, definition);这个方法,这个registryAnnotationConfigServletWebServerApplicationContext,而registerBeanDefinition方法是BeanDefinitionRegistry里的抽象方法,该进入到哪个实现了这个方法的实现类里呢?
在这里插入图片描述
我们再看一下继承关系图:
在这里插入图片描述
很显然会进入这个类的父类GenericApplicationContext
在这里插入图片描述
这个时候进入到了DefaultListableBeanFactory类里面对此方法的实现,也就是Bean注入图中的代码了。

其他的4个也是一样的操作,如此就说明了beanDefinitionMapbeanDefinitionNames的元素的来源了。 到此,B.2.7 加载资源的流程也基本说清楚了,当然还有些细节就不说了,感兴趣的话可以自己再详细看哈~

B.2.8 发布上下文加载事件并处理事件

listeners.contextLoaded(context);
又见listeners,进去后就直接看对应的实现了。此方法里面是一个for循环,然后进行事件发布。
循环的数据是从哪里来的呢?this.application.getListeners()方法返回的监听器集合数据是也~~
我们在2.3.1节-D、设置监听器 分析过,最终有10个监听器,忘记了的再回去看下哈~
在这里插入图片描述
从上图红框中可以看出,这是我们在B.2.2 初始化器应用到上下文分析过的3个初始化器,加上在构造函数的获取的10个监听器共计13个都在上下文context中,打包后一起发布了一个ApplicationPreparedEvent事件,那么这个事件有监听器来接收并处理嘛?搜完发现还不少,拿其中一个LoggingApplicationListener举例,这里往bean工厂里添加了3个单例Bean,如springBootLoggingSystemspringBootLoggerGroupsspringBootLoggingLifecycle,目前bean工厂里有7个特殊的单例Bean,还有一个RestartApplicationListener监听器,也是我们前面反复提到过的,就不分析了,看看这里接收到准备事件之后做了什么?
在这里插入图片描述
至此,上下文准备这步也分析完了,总体来说不是很难,就是需要耐心往回去再看看,不过下面的更新上下文是块难啃的硬骨头了,做好心里准备哦~

B.3、更新上下文

refreshContext(context);
此方法进去后先是往applicationListeners里面又添加了一个类:SpringApplicationShutdownHook,优雅的关闭SpringBoot应用的钩子。连同前面的分析的B.2.8 发布上下文加载事件并处理事件,加上这个钩子,目前应用监听器集合中有14个元素了。
在这里插入图片描述
接着进入AbstractApplicationContext.refresh(context);方法,这个方法里的内容算是目前为止注释写的最好的一个方法了,我把中文注释写在英文注释下面了:

public void refresh() throws BeansException, IllegalStateException {
    
    
		synchronized (this.startupShutdownMonitor) {
    
    
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
			// Prepare this context for refreshing.
			//1、准备更新上下文
			prepareRefresh();
			//Tell the subclass to refresh the internal bean factory.
			//2、告诉子类更新内部的bean工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// Prepare the bean factory for use in this context.
			//3、准备在此上下文中使用的Bean工厂
			prepareBeanFactory(beanFactory);
			try {
    
    
				// Allows post-processing of the bean factory in context subclasses.
				//4、允许在上下文子类中对Bean工厂进行后处理。
				postProcessBeanFactory(beanFactory);
				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				//5、调用在上下文中注册为Bean的工厂处理器
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				//6、注册拦截 Bean 创建的 Bean 处理器
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();
				// Initialize message source for this context.
				//7、初始化此上下文的消息源
				initMessageSource();
				// Initialize event multicaster for this context.
				//8、为此上下文初始化事件广播
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				//9、初始化特定上下文子类中的其他特殊Bean
				onRefresh();
				// Check for listener beans and register them.
				//10、检查监听器的Beans并注册它们
				registerListeners();
				// Instantiate all remaining (non-lazy-init) singletons.
				//11、实例化所有剩余(非懒加载初始化)的单例
				finishBeanFactoryInitialization(beanFactory);
				// Last step: publish corresponding event.
				//12、最后一步:发布相应的事件
				finishRefresh();
			} catch (BeansException ex) {
    
    
				if (logger.isWarnEnabled()) {
    
    
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
				// Destroy already created singletons to avoid dangling resources.
				// 销毁已创建的单例以避免悬而未决的资源
				destroyBeans();
				// Reset 'active' flag.
				//重置active的标识
				cancelRefresh(ex);
				// Propagate exception to caller.
				//将异常告知调用方
				throw ex;
			} finally {
    
    
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

至于里面的catch块中的都比较简单,就没有必要分析了。

B.3.1、准备更新上下文

prepareRefresh();

准备此上下文以进行刷新、设置其启动日期和活跃标识以及执行属性源的任何初始化

进去方法的上半部分是设置启动日期和活跃标识为true和日志的记录,这没啥可说的。

我们这里也分成三个部分:

1)initPropertySources();
2)getEnvironment().validateRequiredProperties();
3)this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);

1)initPropertySources();
此方法,中文注释为:

初始化上下文环境中的任何占位符属性源

进入方法后,再进入ConfigurableWebEnvironment.initPropertySources,来到了StandardServletEnvironment.initPropertySources方法:
在这里插入图片描述
这个方法里只有一个静态方法,但是方法的第一个参数是一个方法getPropertySources(),这个方法返回的是MutablePropertySources,这个类有点眼熟有没有,我们在分析run方法printBanner方法的上半部分A部分A.6.1和A.6.4 有提到过,而在A.6.1、A.6.3和A.6.4 分析的时候基本都在说往这个类的propertySourceList属性放元素,一共是7个,现在我们进入WebApplicationContextUtils.initServletPropertySources方法也可以看到:
在这里插入图片描述
这里的线看着画的有点乱,实际上,由于servletContextservletConfig都是null,所以两个if块都进不去,如果能进去的话就是把蓝色箭头指向的name对应的资源类替换成绿色下划线的类,可惜这里的算盘打错了,没给机会,至于什么时候会进行替换,我想肯定在后面喽,等遇到了我们再说。

2)getEnvironment().validateRequiredProperties();

验证标记为必需的所有属性是否可解析
参见 ConfigurablePropertyResolver#setRequiredProperties

意思就是校验是否缺少必要的属性,由于requiredProperties的size=0,没有对任何属性进行标记为必须,所以就无需校验了,如果缺少必要属性就会直接抛出缺少必要属性的异常,如果后面源码中没有进行设置,那就是可以自定义进行设置了。
在这里插入图片描述
3)this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);

这里就是保存预更新的ApplicationListeners,我们在 B.3、更新上下文B.2.8 发布上下文加载事件并处理事件都有提到,忘记了的话可以再回去看下哦~
在这里插入图片描述
这部分其实就做了2件事,还是之前做过的,就是准备需要的所有的属性资源和所有的监听器。

B.3.2、告诉子类更新内部的bean工厂

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
这里的操作比较少,在GenericApplicationContext.refreshBeanFactory就一个更新Bean工厂:
在这里插入图片描述
一进方法的if块是一个CAS算法,判断refreshed的值是否为false,是的话更新为true,其实我没有弄懂这么判断和操作的意义,是不是将refreshed改为true之后,就表示从这里之后才是开始更新上下文的操作。接着将beanFactoryserializationId设置为和context一样的application,这个我记得也是分析过的,大概在applyInitializers(context)里的某个初始化器做的,方法最终返回beanFactory

B.3.3、准备在此上下文中使用的Bean工厂

prepareBeanFactory(beanFactory);
方法上有行注释:

配置工厂的标准上下文特征,例如上下文的类加载器和后处理器。

将上个方法获取到的beanFactory作为参数传到这个prepareBeanFactory(beanFactory);方法中
方法里大概分成了4个部分进行添加:

beanFactory.ignoreDependencyInterface
beanFactory.registerResolvableDependency
beanFactory.addBeanPostProcessor
beanFactory.registerSingleton

这里罗列一下这几个方法对应的都把元素放到哪里了:

方法 集合对象 元素类型 元素举例
addPropertyEditorRegistrar propertyEditorRegistrars Set< PropertyEditorRegistrar> PropertyEditorRegistrar
addBeanPostProcessor beanPostProcessors List< BeanPostProcessor> ApplicationContextAwareProcessor,ApplicationListenerDetector
ignoreDependencyInterface ignoredDependencyInterfaces Set<Class<?>> EnvironmentAware.class、ApplicationEventPublisherAware.class、pplicationContextAware.class
registerResolvableDependency resolvableDependencies Map<Class<?>, Object> (BeanFactory.class, beanFactory)、(ApplicationContext.class, AnnotationConfigServletWebServerApplicationContext)
registerSingleton registeredSingletons Set< String> “environment”,“systemProperties”
registerSingleton singletonObjects Map<String, Object> (“environment”,ApplicationServletEnvironment)、(“systemProperties”,Properties)

根据字面意思很容易猜出是什么意思,拿最后一个registeredSingletons来说,index=0到3在B.2.6 Bean工厂的设置节分析过,index=4到6的是在B.2.8 发布上下文加载事件并处理事件节分析过,而这里则补充了index=7到10的单例对象,最终的结果就是下图的11个元素:
在这里插入图片描述
其他的就不一一举例了,感兴趣的可以debug看一下里面的元素。

B.3.4、允许在上下文子类中对Bean工厂进行后处理

postProcessBeanFactory(beanFactory);
这个方法是一个被多个子类实现的抽象方法,那么我们该进入到哪个实现类里面呢?我们在分析这个B.3、更新上下文的时候,refreshContext(context);方法里面有个context,而这个context的类型是:AnnotationConfigServletWebServerApplicationContext
在这里插入图片描述
我们再来看下继承关系图
在这里插入图片描述
refreshContext(context);方法进去后,一路往下走,就到了ConfigurableApplicationContext.refresh方法,此方法的真正的实现是在AbstractApplicationContext,还有2个子类(ReactiveWebServerApplicationContextServletWebServerApplicationContext)也重写了refresh方法,但是他们都调用了super.refresh();所以还是来到了AbstractApplicationContext里面,这节分析的postProcessBeanFactory(beanFactory);方法被多个实现类重写,而当前AbstractApplicationContext里面的对象其实还是在SpringApplication中调用refresh方法的AnnotationConfigServletWebServerApplicationContext,不信我们看debug的情况:
在这里插入图片描述
this对象是AnnotationConfigServletWebServerApplicationContext,所以我们进入到这个类里面的实现,而这个类却偷了个懒,又调用了父类的方法:super.postProcessBeanFactory(beanFactory);
在这里插入图片描述
在这个方法中通过断点可以看到下面的两个if块都是false,所以我们只需要分析super.postProcessBeanFactory(beanFactory);方法即可。
从上面的继承关系图中可以看到,这个类的父类是ServletWebServerApplicationContext,在这里才是真的对处理bean工厂的实现:
在这里插入图片描述
这个方法中前面2行和上一节B.3.3、准备在此上下文中使用的Bean工厂中的那个表格中的两个添加元素的两个方法一样,关键我们看蓝色阴影断点处的registerWebApplicationScopes();方法:
在这里插入图片描述
这个方法的前后2行很简单,就不说了,重点看方法第二行的静态方法:WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());

进去后方法上面有行注释:

使用给定的 BeanFactory注册特定于 Web 的范围(“请求”、“会话”、“全局会话”),由 WebApplicationContext 使用

此静态方法只有一行代码,又调用了registerWebApplicationScopes(beanFactory, null);方法,如下图:
在这里插入图片描述
经过断点调试,发现只有红框中的会被执行,两个if块都是false,而红框中的也正如注释所说是注册的Web应用上下文的。第一个红框是注册范围,一个是Request,另一个是Session,注册到解决依赖集合的元素都是和Servlet相关的请求,响应,Session对象工厂类。
所以这个方法主要是处理Web应用的工厂的注册。

B.3.5、调用在上下文中注册为Bean的工厂处理器

invokeBeanFactoryPostProcessors(beanFactory);

这个方法和下面的registerBeanPostProcessors(beanFactory);方法基本都是Spring里面的东西,我就捡重要的分析。

断点进入此方法后,来到了方法中第一行,方法的入参的第二个参数:getBeanFactoryPostProcessors是一个方法,方法返回一个List集合,集合beanFactoryPostProcessors里面有3个元素,这三个元素我们在B.2.6 Bean工厂的设置着重分析过。
在这里插入图片描述
我们进入PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());方法后,当前的beanFactoryBeanDefinitionRegistry的实例,就进入到这个很大的if块中,这个if块大体分成5个部分,

1) 对beanFactoryPostProcessors进行遍历,将是BeanDefinitionRegistryPostProcessor的实例和不是BeanDefinitionRegistryPostProcessor的实例分别放置
2)首先,调用实现 PriorityOrderedBeanDefinitionRegistryPostProcessor
3)接下来,调用实现 OrderedBeanDefinitionRegistryPostProcessor
4)最后,调用所有其他 BeanDefinitionRegistryPostProcessor,直到没有其他处理器出现。
5)最后的最后,调用到目前为止处理的所有处理器的 postProcessBeanFactory 回调。

我们分别说一下:
1) 对beanFactoryPostProcessors进行遍历,将是BeanDefinitionRegistryPostProcessor的实例和不是BeanDefinitionRegistryPostProcessor的实例分别放置
在这里插入图片描述
这个for循环执行完后有一行注释:

不要在这里初始化 FactoryBeans: 我们需要让所有常规未初始化的bean让 beanFactory 后置处理器应用于它们!在实现了PriorityOrdered, OrderedBeanDefinitionRegistryPostProcessors的和其余部分进行分开

这行注释就是针对2,3,4来说的,我们往下看:
2)首先,调用实现 PriorityOrderedBeanDefinitionRegistryPostProcessor
我们先看下postProcessorNames最终的结果,再来分析getBeanNamesForType,此方法在后面会多次用到~
在这里插入图片描述
点击getBeanNamesForType,我们进入DefaultListableBeanFactory.getBeanNamesForType()方法中,由于configurationFrozen的初始值是false,所以在这个值没有被改为true之前,我们都是直接进入这个if块中:
在这里插入图片描述
我们进入doGetBeanNamesForType方法,先是创建了一个result的List集合,接着有两个for循环,我们先来说第一个for循环,这个是对beanDefinitionNames进行遍历的,debug显示里面有7个元素。
在这里插入图片描述
这7个元素是哪里来的呢,我们在 B.2.7 加载资源中最后面的Bean注入图分析过beanDefinitionMapbeanDefinitionNames里面的元素的来源,这里就不赘述了。
接着我们先进入getMergedLocalBeanDefinition方法里,方法中最后一行return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));中的getBeanDefinition(beanName)进去的beanDefinitionMap元素集合也是我们刚才说的在 B.2.7 加载资源分析过。beanDefinitionMapbeanDefinitionNames里的区别是beanDefinitionMap放的是beanName和对应的类,beanDefinitionNames只放了beanName。
在这里插入图片描述
接着再着重说一下要往result中放元素,阻碍放元素的两个布尔值:isFactoryBeanmatchFound,先看isFactoryBean:
在这里插入图片描述
从上图中的FactoryBean.class.isAssignableFrom(beanType)可以看出来是想判断ConfigurationClassPostProcessor是否继承FactoryBean,而这里的isAssignableFrom方法是个native方法,这个方法上的注释有说明这个方法的用途。
所以这里isFactoryBean的值是false,这样就可以进入if块了:
在这里插入图片描述
然后我们就要判断matchFound的值了,对应的方法是isTypeMatch(beanName, type, allowFactoryBeanInit);,这个方法进去代码很多,我没有全部看完,但最后都会走到此方法的最后一行:
return typeToMatch.isAssignableFrom(predictedType);从此方法进入后,方法体依然很多,我直接在return的地方打的断点,这样就直接知道在哪里跳出去的,最终来到了:
在这里插入图片描述
在这个if块中,if判断是一个三目运算,get到了,以后我也这么写。。。
我们进入ClassUtils.isAssignable(ourResolved, otherResolved)方法中:
在这里插入图片描述
这里判断ConfigurationClassPostProcessor是否继承了BeanDefinitionRegistryPostProcessor,我们看下图的ConfigurationClassPostProcessor类的定义,很显然是true,所以matchFound的值也就是true
在这里插入图片描述
所以最后是往result集合中放入了org.springframework.context.annotation.internalConfigurationAnnotationProcessor元素。
在这里插入图片描述
而其他的6个运气就没这么好了,都是走的这个方法的else块,所以matchFound都是false。
在这里插入图片描述
再来说说第二个for循环,循环的集合是manualSingletonNames,里面有11个元素,这个11个元素和我们在B.3.3、准备在此上下文中使用的Bean工厂提到的registeredSingletons的元素一模一样,其实是在这里添加的,只是当时我们没有分析。
在这里插入图片描述
接着对manualSingletonNames进行遍历:
在这里插入图片描述
这里面也是使用的isTypeMatch(beanName, type)进行的判断,11个元素都和第一个for循环除了beanName=internalConfigurationAnnotationProcessor被pass掉了,所以也就有了我们这小节开头说的:postProcessorNames里面只有一个元素internalConfigurationAnnotationProcessor

接着又对postProcessorNames进行了遍历,由于这个集合中只有一个元素,就快多了:
在这里插入图片描述
在if块中,往currentRegistryProcessors放了beanName=internalConfigurationAnnotationProcessor的对应的类,而
processedBeans放的还是这个字符串的beanName。currentRegistryProcessors.add方法的入参是根据beanName拿到对应的Bean,有个beanFactory.getBean方法,这个方法对应的实现类AbstractBeanFactory.doGetBean如果拿不到Bean会调用createBean的方法,就不继续说了,不然真的太多了。

for循环之后,进行了排序,把ConfigurationClassPostProcessor添加到registryProcessors里面,接着就是另外一个需要重点分析的方法了,这个方法在下面也会被反复调用,即invokeBeanDefinitionRegistryPostProcessors,我们进入此方法:
在这里插入图片描述
方法一进来就是一个for循环,重点就是蓝色断点处的方法,这个是一个抽象方法,我们当然是进入ConfigurationClassPostProcessor重写的次方法啦:
在这里插入图片描述
此重写方法前面基本都是判断,我们直接进入方法最后一行也就是画红框的方法内部:
在这里插入图片描述
上图是processConfigBeanDefinitions方法中的2/3处的一个do-while循环的方法,其中我画红框的方法比较值得分析,这两个方法是使得上图中断点处的beanDefinitionMapbeanDefinitionNames的元素由7个激增到142个的方法。第一个红框的parser.parse(candidates);方法是读取了两个properties文件:
在这里插入图片描述
把这两个文件中共861个属性进行配置属性的归类,也就是前面一个图中的configClass的集合中的47个元素,这里面每个元素中有的又有多个元素,由于这个parse方法的嵌套太多了,我这里就不详细说了,感兴趣的可以断点进去查看,但需要有耐心,不然会把人绕懵圈的。我这里就简单说下this.reader.loadBeanDefinitions(configClasses);方法的处理过程。进入此方法后第二行是一个for循环:
在这里插入图片描述
在上图中的箭头指向的方法中有两个画红框的方法,第一个进去后可以找到这行代码:
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
第二个for循环中的红框的方法进去之后可以找到这个方法:
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
这两个方法对应的两个方法的this.registry都是DefaultListableBeanFactory类中的这个方法:
registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
在这里插入图片描述
上图就是真正往beanDefinitionMapbeanDefinitionNames添加元素的地方了。
至此第二步 2)首先,调用实现 PriorityOrderedBeanDefinitionRegistryPostProcessor 就差不多分析完成了。
3)接下来,调用实现 OrderedBeanDefinitionRegistryPostProcessor
我们发现这个地方和第二步的代码几乎一模一样,但看注释可以知道,第二步是找实现了PriorityOrdered 的处理器,而这步是找实现了Ordered的后置处理器。
在这里插入图片描述
很显然postProcessorNames的值还是一样的,对应的类还是ConfigurationClassPostProcessor,这个类是并未实现Ordered
在这里插入图片描述
所以下面的排序等操作执行之后的结果等于和第二步执行的结果一样。
不过要注意invokeBeanDefinitionRegistryPostProcessors方法的第一个入参:currentRegistryProcessors,在第一步最后给clear了,所以方法进去之后for循环也就会直接退出了。
4)最后,调用所有其他 BeanDefinitionRegistryPostProcessor,直到没有其他处理器出现。
这个while循环其实也是相当于啥都没做,下图中画红框的if判断很显然是false,而排序等操作由于currentRegistryProcessors在第一步被clear了,第二步又没有新的元素放进去,且第二步之后也被clear了,所以也无法继续执行了。
在这里插入图片描述5)最后的最后,调用到目前为止处理的所有处理器的 postProcessBeanFactory 回调。
在上图中的debug的结果来看,registryProcessors有3个元素,regularPostProcessors有一个元素都是作为了invokeBeanFactoryPostProcessors方法的第一个参数,对于registryProcessors的循环中前两个元素由于并未实现BeanFactoryPostProcessor.postProcessBeanFactory的方法,只有我们前面分析过的ConfigurationClassPostProcessor才实现了postProcessBeanFactory方法:
在这里插入图片描述
进入上图中画红线的方法之后,我们直接进入了enhanceConfigurationClasses(beanFactory);方法:
在这里插入图片描述
上图中(由于想把代码放在一张图,就对第一个for循环中的if块进行了折叠)是第一个for循环之后结果,最终只有我们自定义的启动类com.fanhf.demo.SpringBootDemoApplication通过了黄色箭头指向的if判断,放入了绿色下划线的configBeanDefs的Map集合中,在第二个for循环的时候对我们自定义的启动类进行加强,再把值赋值给了beanClass,也就是蓝色阴影断点处。

regularPostProcessors集合中的一个元素对应的处理器是SpringApplicatioon中的静态内部类PropertySourceOrderingBeanFactoryPostProcessor实现了postProcessBeanFactory方法:
在这里插入图片描述
这个静态内部类我们好像在哪里见过,在 B.2.6 Bean工厂的设置节中第3部分添加Bean工厂处理器集合到上下文中有分析过,那这个postProcessBeanFactory方法:
而这个moveToEnd方法也好像在哪里见过,没错就是A.6.5 将默认的环境变量移动到最后中一模一样的方法。


说完invokeBeanFactoryPostProcessors方法的上半部分,就是下半部分了,条理还是很清晰的:
从142个beanName中挑出对应的实现了BeanFactoryPostProcessor的bean对应的beanName进行遍历:
在这里插入图片描述
遍历的元素一共有6个,第一个因为processedBeans包含第一个元素直接skip,其他的依次根据符合的条件被放到了不动的List集合中。
放入集合之后,由于priorityOrderedPostProcessors在上图中的for循环的时候就将beanName对应的bean放到了含有BeanFactoryPostProcessor的集合中,而orderedPostProcessorNamesnonOrderedPostProcessorNames则是在下图中的for循环中才完成的,我没明白为啥不和priorityOrderedPostProcessors一样的处理,又何必再进行一次循环的时候才转为对应的bean。
在这里插入图片描述
转为对应的bean之后就各自调用了invokeBeanFactoryPostProcessors方法,各个后置处理器分别进行处理。而最终只有priorityOrderedPostProcessors里面的处理器能干点活,至于干了啥,我就先不说了,后面用到的时候再分析吧。另外两个集合的处理器都不成事,啥也干不了。

我们回到这个小节的最开始的方法invokeBeanFactoryPostProcessors,上面都是对此方法的第一个静态方法的分析,接着是一个if块,这个if条件中有3个条件进行”且“的判断,前两个都是true,但最后一个是判断beanDefinitionMap里面是否有loadTimeVeaver,很遗憾是没有的,所以if里面的操作都无法进行了。
在这里插入图片描述
至此,这个invokeBeanFactoryPostProcessors(beanFactory);方法终于大体分析完了。由于里面基本都是spring的组件(beans,context,core)包里的内容,也都是我之前从来没有看过的,所以这个方法一开始看的时候是真的痛苦,因为走着走着我就不知道自己在哪里了?好在后面静下心来,一点点的看,才渐渐明朗。

B.3.6、注册拦截 Bean 创建的 Bean 处理器

registerBeanPostProcessors(beanFactory);
这个方法进去之后是一个静态方法,即下图中画红框的,而画横线的是我们在B.3.5、调用在上下文中注册为Bean的工厂处理器节中花费很多心思分析过的,是不是看着有点似曾相识的感觉~
在这里插入图片描述
有了B.3.5的基础,这个方法的分析也会变的简单多了。这个方法的实现基本和上一节的静态方法,即上图中画横线的方法的下半部分是同样的操作。
在这里插入图片描述
从143个beanName中挑出对应的实现了BeanPostProcessor的bean对应的beanName进行遍历,最终找到了6个,接着将这6个根据不同的条件放到对应的集合中,再将beanName转为对应的bean类放到对应的处理器的集合中:
在这里插入图片描述
在对orderedPostProcessorNamesnonOrderedPostProcessorNames进行遍历的时候,如果对应的Bean处理器是MergedBeanDefinitionPostProcessor的实例,还会放到internalPostProcessors里面,包括priorityOrderedPostProcessors在内的,这四个处理器集合都会registerBeanPostProcessors方法的洗礼,除了nonOrderedPostProcessors其他三个会再次进行排序。
那么registerBeanPostProcessors方法里面又做了什么呢?
在这里插入图片描述
其实就是把这4个集合中共6个处理器都放到beanPostProcessors里面,
我们先看下在执行第一个registerBeanPostProcessors方法之前里面有哪些元素:
在这里插入图片描述
里面已经有5个,前4个是在B.3.3、准备在此上下文中使用的Bean工厂的表格里提到过,最后一个是我们下图画红线的地方放进去的。
在这里插入图片描述
当我们执行完最后一个registerBeanPostProcessors方法之后,里面有多少元素呢?
在这里插入图片描述
有11个元素了,加上上图中红框的代码执行后就有12个了。
至此,registerBeanPostProcessors(beanFactory);就分析完了,最后回到refresh方法的这部分:
在这里插入图片描述
画绿框的两个方法就是我们在B.3.5B.3.6也就是本小节中分析的。

B.3.7、初始化此上下文的消息源

initMessageSource();

初始化消息源,如果上下文中没有定义就使用父类的。

在这里插入图片描述
从我们分析以及断点的情况来看,确实是没有定义的,所以使用的是代理的消息源,随后注入到单例bean工厂中。

B.3.8、为此上下文初始化事件广播

initApplicationEventMulticaster();

初始化应用事件广播器,如果上下文中未定义则使用SimpleApplicationEventMulticaster

一看注释就感觉和前一个方法:初始化此上下文的消息源是一样的操作。
在这里插入图片描述
正如注释所言,bean工厂中确实没有applicationEventMulticaster,所以使用SimpleApplicationEventMulticaster,随后又往单例bean工厂中注册了这个SimpleApplicationEventMulticaster

B.3.9、初始化特定上下文子类中的其他特殊Bean

onRefresh();
本节分析说明:我只选择部分源码进行分析,主要根据控制台打印的日志来看,其他的感兴趣的可自行断点分析哈~
我们都知道Springboot自带了Tomcat,而Tomcat的默认端口是8080,那么这个8080是在哪里设置的呢?我就在这节就揭晓答案。
onRefresh();进入后,我们接着进入createWebServer();
在这里插入图片描述
在这个方法中的第5行方法:getWebServerFactory();进去之后,也使用到了getBeanNamesForType方法,从143个bean中找到继承了ServletWebServerFactory类的bean,恰好只有一个beanName为tomcatServletWebServerFactory的类TomcatServletWebServerFactory,于是我们就进入此类也就是画红框的getWebServer方法,这是一个抽象方法,我们进入到TomcatServletWebServerFactory重写的此方法:
在这里插入图片描述
getWebServer方法第四行创建了Tomcat类,而我们常用的localhost地址就是在Tomcat类中定义的hostname的属性值和port;
此方法中值得看的还有方法:prepareContext(tomcat.getHost(), initializers);,这个方法里面也有不少东西,是对Tomcat初始化之前的一些操作,我这里就不写了,不然太多了,感兴趣的可以进去看看。
再说回8080端口,我们最终使用的并不是Tomcat类中定义的,而是上图中画红框的customizeConnector方法中:
在这里插入图片描述
此方法中的Math.max(getPort(), 0);中的getPort()方法就是下图中的AbstractConfigurableWebServerFactory类中定义的。
在这里插入图片描述
接着我们来到getWebServer方法的最后一行:return getTomcatWebServer(tomcat);进入此方法,再来到TomcatWebServer的构造函数:
在这里插入图片描述
构造函数中有个 initialize();进去后此方法中有一行日志会打印到控制台中,而控制台中日志中的端口就是我们前面分析的那里放进去的。
在这里插入图片描述
执行完上图中initialize方法的第一行代码,控制台就打印出了下面的日志。看到这里的同学可能会想,我们可以在application.properties文件或xml文件中配置server.port,那么这个是怎么获取的呢?这里就留个作业吧,自行分析和查看。
在这里插入图片描述
整个initialize方法体是一个同步方法,添加了内置的同步器,在同步器中有行代码:this.tomcat.start();start();方法有两个实现类,我们进入LifecycleBase类的方法中,我们进入这个同步方法中,这个方法中有多个分支和状态的不断变化,我最开始看这里的时候,绕的晕头转向,好在后面多走几次,慢慢才理解,偶然一次在看枚举值,发现了Lifecycle类上面有个状态变更的注释,或许看完之后可以不那么迷糊。
在这里插入图片描述
这个状态变动的下面还有文字说明,自取去查看哦~
在try-catch块中有个startInternal方法,这个方法被很多类重写,不断的往下,最终来到了StandardService类中startInternal方法:
在这里插入图片描述
我们看到断点走完上图中的蓝色阴影部分就打印了一行日志:
在这里插入图片描述
那么这行日志Starting service [Tomcat]是从哪里来的呢?答案就在sm.getString方法中的key为:
在这里插入图片描述
后面接着来到了StandardEngine类的startInternal方法:
在这里插入图片描述
key为standardEngine.start,对应value值就是下图中的:Starting Servlet engine: [{0}],{0}里面的值被替换为上图画红框中的数据
在这里插入图片描述
打印到控制台的日志如下图:
在这里插入图片描述
里面的Tomcat的版本号是哪里来的呢,我们在startInternal()方法中的log.info中看到了ServerInfo.getServerInfo())那么版本号肯定是这行代码拿到的,进去后,果不其然在ServerInfo类有个static块:
在这里插入图片描述
块中有个写死的路径的文件:/org/apache/catalina/util/ServerInfo.properties我们进入文件就看到了下图中的信息:
在这里插入图片描述
正好对应日志中的Tomcat版本号;Apache Tomcat/9.0.68
走完这里之后,我真的是右被绕晕了,在哪些状态之间也不知道循环了多少次,最后我耐心被消耗殆尽了,直接就来到了下图的日志打印的地方:
在这里插入图片描述
下图最后一行就是上面的日志打印的地方:
在这里插入图片描述
不要疑惑我的日志打印的时间,没错,我昨天早上分析了点,今天下班后又接着分析,所以日志的时间就差的很多。还有些地方我没有搞懂,后面有时间再看看这里吧。在上图的这个方法中也就是最后一行后面会打印另一行日志:
在这里插入图片描述
在走些过程后,state不同状态变更作为一个循环,而这样的循环又来了好几遍,又经历了些过程,这个onRefresh方法才算完事,我感觉我一步步走,得走24小时估计才能走完,炸了炸了,后面再分析吧~~

B.3.10、检查监听器的Beans并注册它们

registerListeners();
relax!!!! 这个方法就简单多了!
分成三部分:
在这里插入图片描述
1)监听器重新放到新的家
把我们在B.3、更新上下文节说的那14个监听器遍历了一遍又放到另外一个集合中
在这里插入图片描述
2)监听器bean名字重新找个家
这个for循环上面有个注释:

不要在这里初始化 FactoryBeans:我们需要让所有常规 bean 保持未初始化状态,让后处理器应用于它们!

这个调用了我们在B.3.5、调用在上下文中注册为Bean的工厂处理器B.3.6、注册拦截 Bean 创建的 Bean 处理器分析的getBeanNamesForType方法,从所有的beanName中挑出实现了ApplicationListener类的监听器bean名字,共有6个再放到新的集合中。
在这里插入图片描述
3)想要发布早期的应用事件,可想太多了
在这里插入图片描述
从断点上来看,因为earlyApplicationEvents的size=0,所以if块也就进不去了,就这样结束了~

B.3.11、实例化所有剩余(非懒加载初始化)的单例

finishBeanFactoryInitialization(beanFactory);

完成此上下文的 Bean 工厂的初始化,初始化所有剩余的单例 bean

说实话,走了几遍debug之后,发现这里除了把configurationFrozen(是否可以为所有 Bean 缓存 Bean 定义元数据)由false改为true以外,看似很多的代码,但最终因为被过滤又导致没做什么。
1)为此上下文初始化转换服务。
在这里插入图片描述
因为beanFactory中不包含conversionService,所以没法进行set了。
2)注册默认的嵌入值解析程序,主要用于在属性值中进行解析。
在这里插入图片描述
因为embeddedValueResolvers里面已经有了解析器了,所以就不用往下走了。
在这里插入图片描述
其实这个embeddedValueResolvers集合中的元素我们应该在B.3.5、调用在上下文中注册为Bean的工厂处理器节中的invokeBeanFactoryPostProcessors(beanFactory);里面分析的,但由于超过了Springboot的范围,就跳过了,果然,出来混,早晚都要还的。
PostProcessorRegistrationDelegate类中invokeBeanFactoryPostProcessors( Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory)调用了方法postProcessor.postProcessBeanFactory(beanFactory);,此方法是一个抽象方法,找到实现类
PropertyResourceConfigurer中的重写方法postProcessBeanFactory,再调用此方法中的processProperties(beanFactory, mergedProps);方法,此方法也是一个抽象方法,我们进入PropertyPlaceholderConfigurer类的processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)方法,此方法中最后一行doProcessProperties(beanFactoryToProcess, valueResolver);进去此方法最后一行beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);进去就是往embeddedValueResolvers里面添加元素了。
调用过程有点多,就不一一截图看了,只要看着源码,相信聪明的你一定可以找到~
3) 初始化 LoadTimeWeaverAware
在这里插入图片描述
这个也有点戏剧性,一上来的方法也是我们在B.3.5、调用在上下文中注册为Bean的工厂处理器节中的invokeBeanFactoryPostProcessors(beanFactory);里面分析的,从beanDefinitionNames集合中143个beanDefinition中挑出和LoadTimeWeaverAware匹配的beanName,结果尴尬了,一个都没有。
4)允许缓存所有 Bean 定义元数据,而不期望进一步更改
这里就是我开头说的在这个方法finishBeanFactoryInitialization(beanFactory);做的一件实事,更改了configurationFrozen的值为true,而这个值在我们上图中的beanFactory.getBeanNamesForType方法里有调用。
5)实例化所有剩余(非惰性初始化)单例。
beanFactory.preInstantiateSingletons();
这个方法里面算是这5个地方中方法体最多的了,主要就是对beanDefinitionNames集合中143个beanDefinition遍历了2次,一次是对143个bean中的所有非惰性单例 Bean 进行创建和初始化,另外一次是为所有适用的 Bean 触发初始化后回调。
第一个for循环最终是调用了getBean(beanName);的方法,这个方法也应该是在B.3.5、调用在上下文中注册为Bean的工厂处理器节需要着重分析的。
第二个for循环中只有beanName为org.springframework.context.event.internalEventListenerProcessor对应的Bean:EventListenerMethodProcessor,才是SmartInitializingSingleton的子类
在这里插入图片描述
才有资格进入到if块中,而debug也的确进去了:
在这里插入图片描述
接着进入到smartSingleton.afterSingletonsInstantiated();方法里,但却因为条件不满足而啥也没做。

B.3.12、最后一步:发布相应的事件

finishRefresh();

完成此上下文的刷新,调用生命周期处理器的 onRefresh() 方法并发布ContextRefreshedEvent

在这里插入图片描述

这个方法里面又分成了5个方法:

1)清除资源缓存
2)初始化此上下文的生命周期处理器
3)为生命周期处理器进行广播更新
4)发布最终的事件
5)参与 LiveBeansView MBean(如果处于活动状态)。

这里面值得说的也就只有2,3,4,1就不说了,就是清空了集合,而5这个if判断NativeDetector.inNativeImage()B.3.3、准备在此上下文中使用的Bean工厂也有这个判断,当时没有进行分析,可以自行查看作用。
2)初始化此上下文的生命周期处理器
initLifecycleProcessor();
在这里插入图片描述
根据debug,发现bean工厂是有lifecycleProcessor的,是有定义的,如果没有定义就像注释中的提示走else分支了,不过不管是否有定义,最终生命周期处理器都是DefaultLifecycleProcessor

3)为生命周期处理器进行广播更新
getLifecycleProcessor().onRefresh();
这个方法应该是这5个方法中最复杂的一个方法了,getLifecycleProcessor()返回的就是我们上面说的初始化好的DefaultLifecycleProcessor,调用这个类中的onRefresh()方法:
在这里插入图片描述
在这个方法执行完startBeans之后就是将运行中状态设置为true,我们进入startBean方法,先是调用了getLifecycleBeans();方法:
在这里插入图片描述
此方法进去之后,在第三行也调用了我们上节也提到过的beanFactory.getBeanNamesForType();,这次要的类型是Lifecycle,最终只找到了4个继承了Lifecycle的beanName,而在上图代码中的有个if判断就把beanName为lifecycleProcessor给过滤掉了,所以还剩下3个了,即下图中的beans所示的三个key-value元素。
在这里插入图片描述
此方法结束后我们回到startBeans,这里代码并不多,但我的确花了点时间理解里面的一个方法,即phases.computeIfAbsent()的写法,我之前还没有这么写过,头一看有点蒙,于是找了个大佬写的博客,运行了其中的例子,才明白了,这一个方法的确省了很多的代码,不熟悉的也可以看一下并运行里面的例子就能很好的理解了,这里留个作业再来玩一下吧~
在这里插入图片描述
最终forEach方法结束之后,在phases变量对应的Map中放了3个键值对,即上图中断点处的phases对象,这个对象的类型是Map<Integer, LifecycleGroup>,而里面的LifecycleGroup的value值是DefaultLifecycleProcessor
的一个内部类,如下图,所以蓝色阴影的断点处的add方法就是LifecycleGroup类的方法。
在这里插入图片描述
forEach方法结束后,就对phases的Map的value值进行了遍历,执行LifecycleGroupstart方法,而start方法又在for循环中调用了doStart方法。首先遍历的就是LoggingApplicationListener,方法一上来就把这个beanName为spirngBootLoggingLifecycle的缓存给删除了,接着在try-catch中调用了LoggingApplicationListenerstart方法,而此类的start方法和第三次要循环到的webServerGracefulShutdown对应的bean:WebServerGracefulShutdownLifecyclestart方法,都只是将running的状态由false改为了true。
在这里插入图片描述
当循环到了webServerStartStop就不一样了,进入了WebServerStartStopLifecycle类的start方法:
在这里插入图片描述
上图中的start方法是调用了this.webServer.start();方法,而webServer的值是TomcatWebServer,这个WebServerStartStopLifecycle类的构造函数是在createWebServer方法中调用的,也就是我们在B.3.9、初始化特定上下文子类中的其他特殊Bean应该分析到的,但却没有细说的。接着我们来到了TomcatWebServerstart方法:
在这里插入图片描述
执行完上图中蓝色阴影断点处,控制台就打印了下图中的日志。
在这里插入图片描述
4)发布最终的事件
publishEvent(new ContextRefreshedEvent(this));
发布ContextRefreshedEvent事件,那么这个事件只有下面4个类会接收后进行处理。
在这里插入图片描述
比如ClearCachesApplicationListenerSharedMetadataReaderFactoryBean,大致是清对应的缓存。
在这里插入图片描述
在这里插入图片描述
至此B.3、更新上下文就基本分析完成了,当然还有些是本文没有提到的,小伙伴们可以自行断点调试查看哦~

B.4、更新上下文的后置处理

这里就很简单了,因为此方法目前是空的,用户可以自行实现。

B.5、打印应用程序花了多久启动完成

当执行完此行代码之后,就可以看到控制台打印了一句话:
在这里插入图片描述
看完getStartedMessage方法里面的append追加的字符串就一清二楚了。
在这里插入图片描述

B.6、发布上下文开始的事件

listeners.started(context, timeTakenToStartup);
我们似乎又看到了老朋友,进入到started方法中可以看到SpringApplicationRunListeners类中大量的调用了本类中
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction)方法。
在这里插入图片描述
进入到listener.stared方法中,看到有一个行注释的意思是:

上下文已刷新,应用程序已启动,但CommandLineRunnerApplicationRunner尚未调用应用程序运行程序。

进入到调用的SpringApplicationRunListeners.started方法:
在这里插入图片描述
发现此方法无方法体,且被标记为@Deprecateddefault方法。感觉哪里不太对,怎么会没有实现呢?当时没有想明白,反复debug之后在publish方法打了断点,在从publish往上追调用方,才明白真正的实现应该是在EventPublishingRunListener里面实现的,因为SpringApplicationRunListeners只有这一个实现类。我为啥没有从未标记为@Deprecatedstarted方法直接进入到实现类的此方法呢,应该是本能的往下钻了。大意了。
在这里插入图片描述
EventPublishingRunListener里面的started方法中,可以看到发布了事件。
在这里插入图片描述
发布事件之后,最终进入到了AbstractApplicationContext里面的publishEvent方法进行时间的发布。
在这里插入图片描述
进入这个方法multicastEvent就可以看到我们之前分析过的方法了:
在这里插入图片描述
这里就是广播此事件给我们之前分析过的那4个监听器,4个监听器进行判断是否有能力处理这个事件。

那么ApplicationStartedEvent事件是哪个监听器来处理的呢?我试图全局搜索但没有搜到监听此事件的监听器。所以这里我也不太明白没有监听器处理的事件发布之后有啥用呢?留个作业吧!

另外补充一句:这里的started方法和下面的B.9节ready方法都是通过调用context.publishEvent方法,publishEvent再调用multicastEvent方法,再进行事件广播,而我们在A.5节分析的listener.starting方法中是直接调用的multicastEvent方法,我其实没有太明白,为什么startedready方法要多走一段路。存在必有原因,后面再多看看,兴许会明白。

B.7、调用运行器

callRunners(context, applicationArguments);
在这里插入图片描述
由于context里面没有ApplicationRunnerCommandLineRunner,所以runners集合为空,方法到for的时候就直接结束了。那么怎么才能从context中拿到ApplicationRunnerCommandLineRunner呢,答案是,自定义实现类去实现这两个接口中的一个或2个的run方法。

B.8、处理捕捉到的运行的异常

下图中的run方法就是我们本次分析的第二部分,其中有2个try-catch,在catch块中有个处理运行异常的方法,我们来分析一下这个方法里是不是有点东西。
在这里插入图片描述
进入到handleRunFailure方法中:
在这里插入图片描述
可以分析的也就这4个地方了,最后一行是直接抛出异常了。就不分析了。

B.8.1、处理退出的编码

handleExitCode发布了一个事件ExitCodeEvent, 但我依旧没有找到监听此事件的监听器。

B.8.2、发布失败的事件给监听器

进入failed方法中,我们可以看到此方法又是调用了doWithListeners方法,此方法调用了画红线的方法callFailedListener,这个方法中的try块中有个listener.failed(context, exception);方法,是不是就很熟悉了,
在这里插入图片描述
这个failed方法的实现依然是在EventPublishingRunListener类中
在这里插入图片描述
这里的if-else中,无论context是否为空,或是否为活跃状态,都可以保证失败的事件可以进行广播。不过很显然,如果是走的if分支,这里的active属性就是在我们前面分析的更新上下文prepareRefresh的时候执行this.active.set(true);时候设置为true的,如果是false,那就说明还没到更新上下文就出bug了,就走else分支了。那么这个ApplicationFailedEvent事件又是被哪个监听器接收后进行处理的呢?全局搜了下此事件,又是老朋友RestartApplicationListener
在这里插入图片描述
此方法中一直往下走,就到了revome方法,这个方法就是从集合中删除这个context,而这个集合,我记得之前也提过,都是写时复制的集合,但作用不一样,前面提到的是放属性资源的,这个是放上下文的:private final List<ConfigurableApplicationContext> rootContexts = new CopyOnWriteArrayList<>();

B.8.3、进行失败报告

reportFailure(getExceptionReporters(context), exception);
根据方法名字可以看出来是把这次错误进行报告,那是怎么报告的,都做了啥呢?先看下getExceptionReporters(context)方法:
在这里插入图片描述
好吧,又是熟悉的配方,又是熟悉的味道,我们直接全局搜SpringBootExceptionReporter,果然在spring-boot-2.7.5.jar下搜到了它的实现类FailureAnalyzers
在这里插入图片描述
所以getExceptionReporters方法的入参是FailureAnalyzers,即下图中的exceptionReporters集合中只有一个元素,也就是失败分析器FailureAnalyzers,那for循环也就只会循环一次了,调用一次 reporter.reportException(failure)方法。
在这里插入图片描述
从画红线的方法进去就到了FailureAnalyzers的地盘,我们看下此类的reportException方法:
在这里插入图片描述
analyze再继续往下,进入了接口FailureAnalyzer,此接口中只有一个方法:analyze,进入到这个方法的实现类,就来到了这个接口的抽象的实现类AbstractFailureAnalyzer,这个抽象实现类的analyze, 发现其实有很多的分析器,每个分析器都是对错误信息进行定制化的处理。这里就不举例了。感兴趣的可以自己查看。
在这里插入图片描述
当返回了FailureAnalysis类,作为report方法的入参:
在这里插入图片描述
看到方法中第一行的loadFactories方法就本能的搜索FailureAnalysisReporter,果然在springt-boot-2.7.5.jar/META-INF/spring.factories包里找到了实现类:LoggingFailureAnalysisReporter
在这里插入图片描述
我们看一下这个日志记录错误分析报告器对report的实现:
在这里插入图片描述
这里就是将错误信息打印到了日志文件中,另外,我还找到了之前启动spring-boot的失败的时候的日志,原来师从这里,揭开了神秘的面纱。

B.8.4、删除注册的上下文

shutdownHook.deregisterFailedApplicationContext(context);
当程序走到了创建完ConfigurableApplicationContext之后才出现了异常,那么就会执行这个解除注册应用上下文的方法。
此方法是在SpringApplicationShutdownHook类中实现的,而这个类上面有一行注解:

一个Runnable 用作addShutdownHook来执行 Spring Boot 应用程序的优雅关闭。此钩子跟踪注册的应用程序上下文以及通过SpringApplication#getShutdownHandlers注册的任何操作。

我们看下方法的实现:
在这里插入图片描述
其实就是删除了set集合中的应用上下文对象。

B.9、发布准备就绪的事件

listeners.ready(context, timeTakenToReady);此方法和B.6节的started方法差不多,进入listener.ready(context, timeTaken))方法,可以看到注释:

在 run 方法完成之前立即调用,当应用程序上下文具有已刷新,所有CommandLineRunnersApplicationRunners已被调用。

进入到EventPublishingRunListener里面的ready方法,和started方法的操作一样,不过换了事件类。
在这里插入图片描述
发布完事件,对应的监听器收到此事件后,就会进行下一步的处理。那下图中就是对接收到的事件进行处理的监听类RestartApplicationListener了。看到下面的图是不是很熟悉,因为在A.5节的时候,我也截了此图,只不过当时分析的是ApplicationStartingEvent事件。
在这里插入图片描述
至此,Springboot算是启动完成了,我们的寻宝之旅也就此结束啦~

三、作业

根据分析过程中有不懂的地方,我就给自己留个作业,后面再重头看一遍,再来补:
1)使用ConversionService将String类型的数字转为Interger,完成的作业:链接
2)application.properties文件或xml文件中配置server.port,程序中如何获取?
3)ApplicationStartedEvent事件发布之后哪个监听器来处理?
4) computeIfAbsent对应的demo,完成的作业:链接

四、扩展

其实里面有些地方可以进行扩展和学习的地方,比如下面的几种,我感觉可以用在以后得工作中
1)自定义banner
2)事件发布demo
3)自定义错误报告
4)执行自定义的run方法

五、总结

从阅读源码,到一遍遍debug,前后贯穿去理解,发现Springboot的启动过程大量的使用了SPI和事件发布,这两种方式中,我平时也多次使用事件发布来对业务进行解耦,这是很好的一种处理方式,之前不知道我们这边大佬封装的事件处理的思想是哪里来的,现在大概找到出处了,所以阅读源码的过程我感觉是痛并快乐,痛的点是有时候不知道如此实现的原因是什么,看了十几遍,依然没有搞懂是怎么走到某一行代码的,本文中也有说明哪些地方不理解;快乐的点是我终于知道有些日志是怎么出现的了,里面的使用了哪些优秀的思想,也知道了哪些点可以进行扩展,有一种非常醍醐灌顶的感觉。

当然里面依然有我没有看懂的地方,有些地方的理解也许是有待勘误的,所以如果在看的过程中有任何疑问,欢迎留言指教哦~

这次分析SpringBoot源码,从开始着手分析到今天写完博客,差不多断断续续用了一个半月的时间了吧,时间很漫长,但我想对我内功的修炼应该是打开了一个大门,以后再看任何源码,我都可以用在这次分析源码的方法进行其他源码的分析,总之,看完之后真的是有很大的收获,以后再接再厉!!!

猜你喜欢

转载自blog.csdn.net/fhf2424045058/article/details/127654668
今日推荐