概览
如果你对Spring Boot 启动流程还不甚了解,可阅读《Spring Boot 启动流程详解》这篇文章。如果你已了解,那就让我们直接看看prepareContext() 源码。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//将环境对象放到上下文中
context.setEnvironment(environment);
//后置处理应用上下文
postProcessApplicationContext(context);
//应用初始化上下文
applyInitializers(context);
//触发监听器‘上下文准备完成’时间
listeners.contextPrepared(context);
//打印启动信息和激活的profile
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//将 applicationArguments 和 printedBanner 实例注册到bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否设置bean懒加载
if (this.lazyInitialization) {
//是则beanFactory后置处理添加LazyInitializationBeanFactoryPostProcessor实例
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载sources
load(context, sources.toArray(new Object[0]));
//触发监听器‘上下文加载完毕’事件
listeners.contextLoaded(context);
}
通过上面的源码我们可以看出,在准备上下文阶段,spring boot 做了很多事情,这其中主要包括以下内容:
- postProcessApplicationContext(),后置处理应用上下文。
- applyInitializers(),‘应用’初始化上下文。
- listeners.contextPrepared(),触发监听器的‘应用上下文准备完成’事件。
- 懒加载模式为 beanFactory 添加 LazyInitializationBeanFactoryPostProcessor() 后置处理器。
- 加载sources,包含 primarySources 和自定义的 sources。
- listeners.contextLoaded(),触发监听器的‘上下文加载完成’事件。
接下来让我们详细的看看每个步骤具体做了什么。
后置处理应用上下文
postProcessApplicationContext() 上下文的后置处理,这里由于 beanNameGenerator 和 resourceLoader 都为null,addConversionService 为true,故仅对bean工厂设置了 conversionService 转换服务。这里的转换服务为 ApplicationConversionService 类的单例实例。bean工厂为 GenericApplicationContext 无参构造函数创建的 DefaultListableBeanFactory()。
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
}
}
‘应用’初始化器
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
applyInitializers()主要是执行SpringApplication对象实例化阶段从spring.factories配置文件中读取并加载的ApplicationContextInitializer接口实现类的initialize()方法。从方法的注释上看主要是在上下文刷新之前做一些操作。spring-boot-x.x.x.jar 默认的spring.factories中包含以下这些实现类:
DelegatingApplicationContextInitializer | 执行一些其他的ApplicationContextInitializer,这些ApplicationContextInitializer来源于context.initializer.classes属性配置;相当一些自定义的初始化器委托给DelegatingApplicationContextInitializer执行 |
ContextIdApplicationContextInitializer | 构建一个上下文ContextId对象并注册到bean工厂中,这里的id一般为从environment环境对象中读取的spring.application.name属性配置,因此一般就是你设置的项目名 |
ConfigurationWarningsApplicationContextInitializer | 添加一个ConfigurationWarningsPostProcessor后置处理器到bean工厂后置处理器中;该后置处理器主要校验@ComponentScan扫描的包或类所在包是否包含org.springframework或org,包含则进行warning级别的提示 |
RSocketPortInfoApplicationContextInitializer | 将上下文对象包装成一个listener监听器添加到当前应用上下文的applicationListeners监听器列表中;包装的listener主要监听了RSocketServerInitializedEvent事件,将获取的端口设置到类型名为server.ports的属性map中,key为local.rsocket.server.port |
ServerPortInfoApplicationContextInitializer | 将自身添加到当前应用上下文的applicationListeners监听器列表中;ServerPortInfoApplicationContextInitializer本身也实现的ApplicationListener接口,主要监听了WebServerInitializedEvent事件 |
以上表格的ApplicationContextInitializer都是按优先级排过序的,程序执行时就是从上到下执行。另外实际断点的过程中,我们可以看到除了以上的initializer之外,还有一些其他jar包中的initializer,如下:
- org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
- 将CachingMetadataReaderFactoryPostProcessor添加到上下文对象的beanFactoryPostProcessors列表中
- io.dubbo.springboot.DubboConfigurationApplicationContextInitializer
- environment环境中是否设置spring.dubbo.scan属性,如果设置了则实例化一个AnnotationBean,并注册到bean工厂中,同时添加到上下文对象的bean工厂后置处理器列表中和beanFactory对象的bean后置处理器列表中。
- org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
- 构建一个ConditionEvaluationReportListener监听器并添加到当前应用上下文的applicationListeners监听器列表中;同时从bean工厂中获取ConditionEvaluationReport。ConditionEvaluationReportListener监听所有的ApplicationEvent事件并触发ConditionEvaluationReportLoggingListener的onApplicationEvent()方法。主要目的打印条件评估报告,以帮助开发者了解Spring Boot 启动过程中自动配置的细节。
实际执行顺序如下图所示:
这里解释下GenericTypeResolver.resolveTypeArgumen() 方法作用,其主要是解析initializer类实现泛型接口的泛型类型,从而判断当前context是否是initializer类实现接口的泛型类型的子类。这里有点绕口,大家可以理解为 ApplicationContextInitializer 接口的泛型类型必须和context接口类型一致,代码上看ApplicationContextInitializer接口的泛型类型就是ConfigurableApplicationContext 类型,所以感觉这里的判断有点多此一举。
触发监听器‘上下文准备完成’事件
listeners.contextPrepared() 表示在上下文准备完成阶段要触发的操作。该事件触发的流程与listeners.starting() 执行流程一致,这里不再讲解,如不清楚可先看看《Spring Boot 启动流程详解》这篇文章。通过打断点可以看到,这里监听了 ApplicationContextInitializedEvent 事件的监听器有:BackgroundPreinitializer、DelegatingApplicationListener、DubboHolderListener;实际这三个listener 都未有 ApplicationContextInitializedEvent 事件的处理逻辑,这里之所有会被扫描出来,是因为其实现的接口泛型类型为 ApplicationContextInitializedEvent 的父类或者未指定,其中 DubboHolderListener 属于未指定。
打印启动信息和激活的profile
上面表格之所以没有列这一步,是因为这里只是打印启动信息及激活profile,没有这一步骤对框架执行并不影响,所以也不重要。
注册 ApplicationArguments 和 Banner bean,设置bean是否可以被覆盖
这里通过 DefaultListableBeanFactory 将 ApplicationArguments 和 Banner 实例对象以单例的方式进行注入,同时设置不允许 bean 被覆盖,让我们看看 registerSingleton() 方法源码:
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
//调用父类DefaultSingletonBeanRegistry的registerSingleton() 方法
super.registerSingleton(beanName, singletonObject);
//更新工厂内部维护的单例bean名称
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
//清除映射
clearByTypeCache();
}
源码很简单,调用父类的 DefaultSingletonBeanRegistry 的 registerSingleton() 方法,让我再看看 DefaultSingletonBeanRegistry 类的 registerSingleton() 方法。
@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
Assert.notNull(beanName, "Bean name must not be null");
Assert.notNull(singletonObject, "Singleton object must not be null");
synchronized (this.singletonObjects) {
Object oldObject = this.singletonObjects.get(beanName);
if (oldObject != null) {
throw new IllegalStateException("Could not register object [" + singletonObject +
"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
}
addSingleton(beanName, singletonObject);
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
其逻辑也很简单,就是先加锁,再判断内部维度单例bean的map是否包含当前bean,包含则抛出异常,单例bean不能被重复创建。如果不存在,则加到缓存map中。
加载sources
先看看源码~
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
//创建一个BeanDefinitionLoader用于加载bean
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
//进行bean的装载
loader.load();
}
getBeanDefinitionRegistry() 方法用于从上下文中获取bean工厂,断点可以看到返回的是context自身,因为其实现了BeanDefinitionRegistry接口。createBeanDefinitionLoader() 方法就是调用 BeanDefinitionLoader() 的构造函数,函数入参包含当前上下文context 和 sources,这里sources主要就是 primarySources,而 primarySources 就是Spring Boot 启动类。让我们再看看 BeanDefinitionLoader() 构造函数。
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
//创建注解bean定义reader
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
//创建xml bean定义reader
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
//如果有加载groovy.lang.MetaClass,在创建groovy定义bean的reader
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
//创建类路径bean定义扫描器
this.scanner = new ClassPathBeanDefinitionScanner(registry);
//添加类排除过滤器
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
BeanDefinitionLoader 构造函数主要就是创建各种bean定义解析器用于bean的定义加载。接下来就是调用创建好的 BeanDefinitionLoader 的 load() 方法,其主要作用就是加载定义的bean,包括注解定义、xml定义、groovy脚本定义以及classPath定义。由于这部分实现比较复杂,这里不做详细说明,后面会单独写一篇文章进行详细介绍。
触发监听器‘上下文加载完成’事件
一样的事件处理流程就不再多说了,这里主要看看执行了哪些listener的相应事件。同样断点可以看到有如下监听器监听了 ApplicationPreparedEvent 事件,这里也通过表格展示下每个监听器具体干了什么。
CloudFoundryVcapEnvironmentPostProcessor | 从内部定义的延迟日志DeferredLog切换到实时日志并进行打印 |
ConfigFileApplicationListener | 添加一个属性源后置处理 PropertySourceOrderingPostProcessor 到bean工厂后置处理器列表中 |
LoggingApplicationListener | 注册springBootLoggingSystem、springBootLogFile、springBootLoggerGroups等相关bean |
BackgroundPreinitializer | 无响应的事件处理 |
DelegatingApplicationListener | 无响应的事件处理 |
DubboHolderListener | spring-boot-dubbo-starter-1.0.0.jar中的监听器,内部啥也没干。。。 |
至此,spring boot 在启动时的上下文准备阶段主要就干了这些事。
注:spring boot 版本为2.3.10
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Spring Boot 项目启动时在 prepareContext 阶段做了哪些事?
发表评论 取消回复