【源码】SpringBoot 启动流程 (五)

77 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

10.准备上下文环境 prepareContext

进入prepareContext()方法如下:

1.将环境 enviorment 设置给上下文 context

设置环境对象,传入的对象是解析完毕 profiles 的对象,Context 内部则是不完整的对象.

2.设置上下文参数

进入postProcessApplicationContext()方法如下:

2.1 以单例模式注册 beanNameGenerator,beanName 生成器, 用来生成 bean 对象的名称

2.2 为上下文设置资源加载器和类加载器

2.3 为上下文设置转换服务

经过 DEBUG,实际上只有 2.3 运行了。

3.加载 ApplicationContextInitializers

进入applyInitializers()方法如下:

首先调用getInitializers()方法,获取所有的 initializer 初始化器,初始化器是在之前创建 SpringApplication 实例时就已经加载好的。getInitializers() 方法内部对其进行了排序然后返回。然后循环执行 initializer 的initialize()方法,SpringBoot 中自带的 initializer 有如下 7 个:

3.1 DelegatingApplicationContextInitializer

用于初始化配置文件中context.initializer.classes属性中指定的 ApplicationContextInitializer 实现类。源码如下:

在 SpringBoot 中实现 ApplicationContextInitializer 并确保其被加载有三种方法:

  • 直接使用代码添加
new SpringApplication(Example.class).addInitializers(new 实现类());
  • 以 SPI 方式配置
在 /META-INF/spring.factories 文件中配置:
org.springframework.context.ApplicationContextInitializer=实现类A, 实现类B
  • 以配置文件方式配置
context.initializer.classes = 实现类A, 实现类B

而这里,DelegatingApplicationContextInitializer类的作用就是从配置中拿到初始化器,创建其实例,并执行其 initialize() 方法。

3.2 SharedMetadataReaderFactoryContextInitializer

向上下文中,添加了一个CachingMetadataReaderFactoryPostProcessor的Bean工厂后置处理器。

具体作用,TOOD.

\

3.3 ContextIdApplicationContextInitializer

用来设置上下文 context 的 id。

\

3.4 RestartScopeInitializer

没太懂

\

3.5 ConfigurationWarningsApplicationContextInitializer

向上下文中,添加了一个ConfigurationWarningsPostProcessor的Bean工厂后置处理器,用于打印配置错误的日志。

\

3.6 ServerPortInfoApplicationContextInitializer

用于设置配置文件中server.ports属性的值,它是Web服务器实际监听的应用程序端口。同时实现了 ApplicationListener 接口用于监听 WebServerInitializedEvent 事件,WebServer 初始化完毕之后设置端口。

① 向上下文监听器列表中添加当前类(作为监听器加入)

② 在监听事件中设置端口,默认属性为local.server.port

③ 若设置了服务器Namespace,则属性值为local.Namespace的值.port

④ 将监听端口信息填入server.ports属性下

\

3.7 ConditionEvaluationReportLoggingListener

虽然名字是以 Listener 结尾,但它实现的是 ApplicationContextInitializer 接口,即实际上是 Initializer,其目的是将 ConditionEvaluationReport 写入到日志,使用 DEBUG 级别输出。程序崩溃报告会触发一个消息输出,建议用户使用调试模式显示报告。

用于设置 ConditionEvaluationReportListener 监听器,此监听器监听 ContextRefreshedEvent 和 ApplicationFailedEvent,分别打印出上下文容器成功刷新和失败的日志报告。

4.执行监听器的准备上下文事件

SpringApplicationRunListeners#contextPrepared()方法触发。

当上下文 context 加载完所有的 Initializer 之后,就会触发该事件,通知那些关注了此事件的监听器进行下一步操作。每次发布事件的时候可能已经有新的监听器被加进来,但我们只需要关注那些监听了当前事件的监听器即可。

5.注册 springApplicationArguments

从 context 中获取 beanFactory,并以单例模式,注册 springApplicationArguments 对象,beanName 为 springApplicationArguments。

6.注册 printedBanner

将 printedBanner 对象注册到 beanFactory,beanName 为 springBootBanner。

7.设置是否允许覆盖 BeanDefinition

allowBeanDefinitionOverriding 属性用来设置是否允许覆盖 BeanDefinition,对应的配置为spring.main.allow-bean-definition-overriding设置为 true 时,后定义的 bean 会覆盖之前定义的相同名称的 bean。

在 SpringBoot 中,该值默认为 false。而在 Spring 中,该值默认为 true。

关于 blog.csdn.net/liubenlong0…

blog.csdn.net/mrlichengyi…

8.加载资源,注册主启动类

首先是获取了加载的 bean 所在的位置,这里是主启动类所在的包。然后调用 load() 方法将主启动类进行注册。

这个方法运行完毕后,主启动类便被实例化并加入到了 beanDefinitionMap 中。

进入 load() 方法如下:

首先是创建了BeanDefinitionLoader对象。BeanDefinitionLoader 是负责加载 Bean 的工具。进入createBeanDefinitionLoader()方法如下:

方法内部是创建了 BeanDefinitionLoader 对象。而 BeanDefinitionLoader 的构造方法中,则创建了从不同形式获取 Bean 的加载器类。

BeanDefinitionLoader 对加载 Bean 的整体功能做了封装,内部由不同的资源加载类来完成不同类型的资源加载,例如:

AnnotatedBeanDefinitionReader 从基于注解的类来开始加载。

XmlBeanDefinitionReader 从 XML 文件开始加载。

GroovyBeanDefinitionReader

ClassPathBeanDefinitionScanner 从 classpath 下加载。

然后是执行中间的 三个 if 判断,这里是都不满足,未执行。

最后执行 BeanDefinitionLoader 的 load() 方法,如下:

这里的 count 用来统计加载的资源数量。

load() 方法中,根据 source 的类型,使用不同方式加载:

如果是 Class,尝试使用 Groovy 和 注解 的方式进行加载。

如果是 Resource,尝试使用 Groovy 和 xml 的方式进行加载。

如果是 Package,尝试使用 注解 的方式进行加载。

如果是 CharSequence,尝试使用 xml 和 注解 的方式进行加载。

这里传入的是 Class,进入 load() 方法如下:

首先判断了 Groovy 方式能否加载,肯定是不能。

然后哦安短了 class 类上是否存在@Component注解,有该注解才会注册,虽然启动类上没有该注解,但是其注解的子注解中存在,所以,这里使用注解的方式,来注册启动类。

进入register()方法如下:

实际上调用的是doRegisterBean()方法,进入该方法:

8.1 首先,创建了AnnotatedGenericBeanDefinition对象,这是注解方式的 Bean 对应的 BeanDefinition。其类结构如下:

8.2 判断是否有@Conditional注解,以确定是否满足条件需要排除

8.3 指定了创建 bean 的回调,这里传入的是 null

8.4 获取当前 Bean 的作用域范围,并设置给 AnnotatedGenericBeanDefinition 对象。

进入resolveScopeMetadata()方法,

首先创建了ScopeMetadata对象,ScopeMetadata 包含类的作用域描述 singleton / prototype 和代理模式描述 ScopedProxyMode,默认为单例模式,不使用代理。

然后判断 Bean 上是否存在@Scope注解,有的话就给 ScopeMetadata 设置对应的属性,没有的话,返回默认的 ScopeMetadata。

8.5 获取 beanName

这里的 name 是入参中传入的,但是传入的是 null ,所以还需要调用beanNameGenerator.generateBeanName()方法来生成 beanName。

这里是注解方式的 Bean,其 beanName 生成规则,其他文章细说,这里略过。

8.6 处理常用注解

AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);的方法名上推出这是处理了常用的注解。

进入该方法中可以看到,常见的注解包括 @Lazy、@Primary、@Role、@Description、@DependsOn,都被解析并设置到 AnnotatedGenericBeanDefinition 的对应属性中。

8.7 检查传入的 Qualifiers

这里方法入参中传入的是 null,所以没有进行任何处理。

8.8 暂时不知道干啥的,以后再说

8.9 注册 Bean,如果使用代理的话,创建对应代理

首先创建了 BeanDefinitionHolder 对象,然后在AnnotationConfigUtils.applyScopedProxyMode()方法中,对其进行处理,如果不使用代理,则直接返回 BeanDefinitionHolder,如果使用了代理,则通过 JDK 代理还是 CGLIB 代理生成对应代理对象。

最后呢是注册 Bean.

\

9.触发 listener 的上下文加载完毕事件

进入contextLoaded()方法,方法内部遍历了 listeners,并执行其 contextLoaded 方法。这里的 listeners 只有一个 EventPublishingRunListener。

在真正触发事件之前,它处理了 ApplicationContextAware 实现,为其设置了上下文 context。