SpringBoot成长记6:准备SpringContext容器

SpringBoot成长记6:准备SpringContext容器

上一节的创建了容器对象,核心就是创建了Context和BeanFactory对象,内部初始化了Reader和Scanner,加载了一些内部Bean等。

已经分析的逻辑代码如下:

public ConfigurableApplicationContext run(String... args) {
   //DONE 扩展点 SpringApplicationRunListeners listeners.starting();
    
   //DONE 配置文件的处理和抽象封装 ConfigurableEnvironment
    
   //容器相关处理
   //1)核心就是创建了Context和BeanFactory对象,内部初始化了Reader和Scanner,加载了一些内部Bean
   context = createApplicationContext();
   exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                          new Class[] {ConfigurableApplicationContext.class }, context);
    //2) TODO 容器对象中还需要准备哪些东西?
    prepareContext(context, environment, listeners, applicationArguments,printedBanner);
    //3) TODO
	refreshContext(context);
   //其他逻辑
}

这一节我们来看看,创建容器后,容器对象中还需要准备或者说设置哪些东西,并且还执行了容器哪些扩展点呢,一起来看下吧!

prepareContext()的核心脉络

prepareContext()说白了点其实就是给容器Context和容器DefaultListableBeanFactory设置一些属性。 你带着这个思路去理解,就会抓大放小,关注核心即可。大致如下图:

SpringBoot成长记6:准备SpringContext容器

那么接下来,就来看下代码到底做了些什么?

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

先给大家概括一张图,主要可以划分为三部分非常重要的操作、值得一提的操作、不重要的操作,如下图:

SpringBoot成长记6:准备SpringContext容器

接下来分别带大家一起仔细分析下。

触发的扩展点(非常重要的操作)

1)applyInitializers()触发的扩展点操作

这个扩展操作,其实就是执行了List的一系列扩展操作。你还记得么?

这个List是在new SpringApplication的时候扫描到的类。还记得下图么:

SpringBoot成长记6:准备SpringContext容器

那么重点是这些扩展操作做了什么呢?

其实概括成一句话就是:Initializers之后,为context初始化了2个BeanFactoryPostProcessor ,补充了3个ApplicationLisenter

具体每一个ApplicationContextInitializer的执行你可以通过断点自己仔细看下知道了,其实没有什么复杂的逻辑。

整体如下图所示:

SpringBoot成长记6:准备SpringContext容器

术语普及BeanFactoryPostProcessor是什么?

BeanFactoryPostProcessor是什么?
之前我们遇见过扩展点有SpringApplicationRunListeners和ApplicationContextInitializer
SpringApplicationRunListeners通过一个EventPublishingRunListener,对一个List<ApplicationLisenter>做不同的事件广播做对流程、容器和配置进行 扩展。

ApplicationContextInitializer 通过List<ApplicationContextInitializer> 其实也是整个流程中的一个扩展。

而这里BeanFactoryPostProcessor其实对容器特有的扩展,或者说是增强处理。 在容器的使用过程中,执行List<BeanFactoryPostProcessor> 对应的方法,进行扩展点的执行。很多第三方框架就是从这地方入手进行扩展的,之后会看到的。

2) 触发listener对容器扩展操作。

除了上面ApplicationContextInitializer的扩展执行,另一个扩展操作的执行就是SpringApplicationRunListeners的扩展了。

主要有两次触发,listeners#contextPrepared()和listeners.contextLoaded(context);

之前我们分析过,SpringApplicationRunListeners通过一个EventPublishingRunListener,对一个List做不同的事件广播做对流程、容器和配置进行扩展。这里广播的是contextPreparedEvent,contextLoadedEvent。

具体细节也不在这里展开了,简单的,这两个事件分别可以概括为如下两句话:

contextPreparedEvent的这里执行了2个ApplicationListener的实现,只不过这两个listener的onApplicationEvent几乎是什么都没做,只是注册两个日志对象到容器DefaultListableBeanFactory的singletonObjects属性。

contextLoadedEvent执行了,4个ApplicationListener,其中1个Listener往容器Context中增加了BeafactoryPostProcessor其余四个Listener基本上什么都没干。

SpringBoot成长记6:准备SpringContext容器

可以看出,这几个扩展点核心其实也没有做很复杂的事情,就是给容器对象补充设置了一些属性而已。可以概括如下图所示:

SpringBoot成长记6:准备SpringContext容器

beanFactory的一些属性补充(值得一提的操作)

除了上述比较重要的操作外,prepareContext中还有一些比较值得一提的操作。让我们

1)beanFactory.registerSingleton

注册两个对象到容器springApplicationArguments、springBootBanner到beanFactory的 singletonObjects 属性

2)补充BeanDefinition

BeanDefinitionLoader.load() 补充了 LearnSpringBootApplication 的BeanDefinition到beanFactory中。

这些都比较简单,整体如下图:

SpringBoot成长记6:准备SpringContext容器

其实这里关键的是容器内的两个属性的设置:

一个是【核心属性】Map<String,Object> singletonObjects 容器存放Bean对象的集合

一个是【核心属性】Map<String, BeanDefinition> beanDefinitionMap 容器存放beanDefinition对象的集合

这个是我们这里想要强调的一点。

设置几个属性或者组件(不重要的操作)

1)context#setEnvironment () 设置envrioment 到context中,也就是让容器持有配置文件的封装对象而已。

2)resourceLoader 设置resource类加载器 到context容器 ,默认没有resourceLoader ,所以这里什么没干。

3)addConversionService 添加转换器和格式化器 到context容器,不知道这个组件时做啥的,暂时不是很重要。

4)logStartupInfo() 输出启动日志-PID,启动文件目录

5)logStartupProfileInfo() 输出启动日志-使用的profile

6)设置容器属性lazyInitialization、allowBeanDefinitionOverriding,默认都是false,不懒加载和不覆盖BeanDefinition。

SpringBoot成长记6:准备SpringContext容器

小结

说白了,prepareContext()就是给容器Context、BeanFactory设置了一堆属性和组件,执行了initialize/listener的扩展点。

主要给容器如下几个核心属性设置值:

singletonObjects 、beanDefinitionMap 、beanFactoryPostProcessors、applicationListeners。

prepareContext()准备完成之后,接下来就是容器关键的扩展操作执行了,也是很多容器功能和第三方功能的扩展之处,我们下一节来一起看下吧!

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

上一篇:beanFactory创建流程 以及BeanDefinition的装填 自定义spring标签


下一篇:JAVA 面试遇到的常见问题