3.准备上下文
看了这么多和参数配置以及外围相关的代码,到这里终于要开始看和核心的context相关的代码了。
这里我们只看主流程的这一行代码:
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
这里的代码,对应的方法具体如下:
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(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);
}
3.1 继续完善上下文
这里涉及代码如下:
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
所做的工作是围绕完善context中相关属性的。
3.1.1 配置环境
第一步是把environment放到context里面,这个行为也好理解,这样子在上下文这个定义上,context对象包括的信息也就更完整了,后面要使用environment信息的时候直接从context里面取,也减少了参数的传递。
context.setEnvironment(environment);
3.1.2 前置处理context
postProcessApplicationContext(context);
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(context.getEnvironment().getConversionService());
}
}
在这一步中,对context进行了context一些关键属性的注入,到这里context才算是开始具有了一些context应有的核心功能。
这里遇到了一个问题:
-
其实在我们上面创建context的时候,其实调用的只是:
new AnnotationConfigServletWebServerApplicationContext(),对应的构造方法如下:
public AnnotationConfigServletWebServerApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); }
这里跟beanFactory没有一分钱关系,那么为什么到了注册这里的getBeanFactory可以拿到东西?
- 而且,如果我们在构造方法中打断点,可以知道在第一行之前,getBeanFactory已经可以拿到东西了。
这里就是继承的一个特点了:
- 创建一个子类对象时,会先调用父类的构造方法,即使没有super关键字。
此时根据继承关系上溯,在GenericApplicationContext的构造方法中发现了:
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
这也就解释了这里的beanFactory哪里来的,我们也清楚这个是哪个类了。
这里Spring中,第二个核心类出现了:
- BeanFactory
我们来解析DefaultListableBeanFactory这个类。
继承关系:见图www.processon.com/view/link/6…
根据类图很容易地判断出这里的registerSingleton对应的代码如下:
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { super.registerSingleton(beanName, singletonObject); updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName)); clearByTypeCache(); }在额外信息中,我们再来重点关注这个代码。
回到主流程:
主流程中,这里根据配置开关情况,至多:
-
在beanFactory中注册了一个单例bean:beanNameGenerator
- 由于我们这里设置是null,因此这一步并不会触发
-
为context设置资源装载器
-
为context设置类加载器
-
根据设置,为beanFacotry设置ConversionService
- 这里其实就是我们之前放到context里的环境对象的转换服务,对应的类其实是ApplicationConversionService。
3.1.3 设置初始化器
实际上这里就是将之前在构造方法中配置的initializer,分别判断并执行初始化的操作。
- 这里指的初始化,并不是构造方法,而是接口的实现。
这里就显示出设计上下文context这一对象的好处了:只要拥有了这一context对象,那么无论是哪个地方都可以知道当前应用的大部分信息,而不需要根据需要在方法中定义不同的入参,这里就是框架的设计之一:统一处理。
- 就比如这里的initializer,拥有了context对象就知道了需要的所有信息,只要按需要取即可。
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);
}
}
3.2 外部通知相关步骤
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
这里实际上是发了一些通知,以及日志的打印。
- listener中定义了若干接口,这个也是一样的,就不多做赘述
- 而bootstrapContext实际上也是相同的,根据事件来对不同的监听者发送了这一事件。
- 下面的日志也算是一个外部通知吧,记录了这一事件。
3.3 添加boot特殊单例bean
告知完外部此时正在初始化后,接着流程对beanFactory做了一些针对的特殊处理。
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());
}
这里大概都可以看懂,就是注册了两个单例bean,并设置了相应的值。
- 这里设置了单例bean包括:main方法传递下来的参数包装类,以及打印的banner。
- 这里可能配置了两个变量:
- AllowBeanDefinitionOverriding
- LazyInitializationBeanFactoryPostProcessor
具体的内容我们到beanFactory的额外部分来看看这些东西具体功能是什么,暂不在流程中详细说明。
3.4 装载资源
配置完contexr在该流程中的一些特殊配置之后,此时就到了准备上下文的最后一步:
- 装载资源。
这部分对应的代码如下:
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
我们一个一个方法来看。
3.4.1 获取所有资源
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}
这里两个点注意一下:
- primarySources这里的一般值是我们的启动类。
- 第二个sources这里暂时是空的,不清楚什么时候会被注入,在对应的方法里也没找到,可能是当我们自定义启动流程的时候,可以通过这个方法来做些什么?留个楔子后面看到的时候补充一下。
3.4.2 装载资源
既这部分的核心主题是装载资源,那么下面这个方法就应该是这部分的核心内容了:
load(context, sources.toArray(new Object[0]));
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
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);
}
loader.load();
}
3.4.2.1 构建BeanDefinitionLoader
这里先创建了一个BeanDefinitionLoader,这里的步骤也很简单:
-
先把context根据继承关系,转换成beanDefinitionRegistry对象(类图中可以看到,这个就是向上转型了)
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) { if (context instanceof BeanDefinitionRegistry) { return (BeanDefinitionRegistry) context; } if (context instanceof AbstractApplicationContext) { return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory(); } throw new IllegalStateException("Could not locate BeanDefinitionRegistry"); } -
随后通过构造方法创建。
BeanDefinitionLoader是用来读取bean定义的,其中配置了可能需要的所有解析器以及扫描器。
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
3.4.2.2 配置loader
创建完这个bean定义加载器之后,根据开关设置了loader的一些属性。
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
3.4.2.3 开始装载
整了这么一大堆,终于开始装载了:
loader.load();
void load() {
for (Object source : this.sources) {
load(source);
}
}
具体是如何装载的,放到后面和BeanDefinitionLoader具体讲一讲。
总的来说,装载完之后,这些bean就会被注册到beanRegistry中。
总结
这一步进行完了之后,上下文的准备工作就结束了,按照常理来说应该就是来使用这个上下文来进行创建工作了。
这里开始,流程进入核心部分了,新的Spring核心类开始陆续出现在主流程中:
- BeanFactory
- BeanDefinitionLoader
接下来在额外信息部分会来做详细的解析。
源码看到这里,Spring作为一个框架,其代码的优秀在设计上就可以看出来了,比如:
- 使用了上下文来做统一入参,这样子就可以定义一些通用的接口来做管理,符合了最小知识原则:不同的类之间所需要知道的信息越少越好。
- 这样子的好处就在于,流程中就不需要对大部分例如listener模式下不同的监听者做很麻烦的统一规范了,各个listener各取所需即可。
- Spring中各个组件、方法之间的职责以及分层非常清楚,命名也很简单易懂。实际上启动流程的run方法是一段事务脚本类型的代码。这种类型的代码如果在分层不明确职责划分不明确的情况下产生,那么会导致这段代码涉及到很多不该有的信息,造成阅读维护上的困难,而这个run方法很巧妙地使用了各个组件进行组合,规避了这一缺点。