前言
在上文中我们讲了SpringBoot的启动流程中SpringApplication的初始化、环境准备、容器创建,在本文中我会带大家来了解SpringBoot的最后一个流程:填充容器,即以下方法:
填充容器
prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境
context.setEnvironment(environment);...(1)
//对容器相关属性进行了填充
this.postProcessApplicationContext(context);...(2)
this.applyInitializers(context);...(3)
//监听器发布上下文准备事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
// 获取Bean工厂,并且注册相关对象的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);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 将启动类注册到Bean工厂
this.load(context, sources.toArray(new Object)...(4)
listeners.contextLoaded(context);
}
进入(1):
该方法会通过上文中进行环境准备获取的环境,对容器环境进行设置。
进入(2):
该方法同样很简单,对容器的类加载器、资源加载器和bean工厂属性进行了填充设置。
进入(3):
该方法会获取在SpringAplication的初始化阶段加载spring.factories文件中的上下文初始化,并且进行遍历对容器进行初始化。
我们随便进入多个实现类查看:
如上两个方法会对容器属性进行填充,如第一个方法会添加Bean工厂的后置处理器。
进入(4):
该方法首先会获取Bean定义读取,然后调用其load方法,进入最底层查看:
refreshContext
进入方法最底层:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();...(1)
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);...(2)
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);...(3)
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
进入(1):
大致可以知道该方法对容器进行了填充
进入(2):
该方法对BeanFactory的相关属性进行了设置,并且注册了一些单例,例如环境、系统属性等。
接下来我会带大家来深入两个比较关键的点,一个是扫描指定报下的类将其注册到Bean工厂,另一个是自动装配的原理。
进入(3):
ComponentScan扫描
继续进入:
该方法对Bean工厂做出了一些处理,添加了一些后置处理器,我们接着往下看:
我们进入这个方法里:
可以看到这里只有一个关于配置类的后置处理器,那么该方法应该就是通过后置处理器加载配置类,我们查看具体的方法:
继续进入:
在该方法中,我们可以发现创建了一个关于配置类解析的对象,并调用parse方法进行解析,并且返回一些配置类,但是parse方法传入了一个变量,我们不知道是什么,到方法上面查看:
我们就可以知道这个变量就是Bean的全类名,大家是否记得在进行刷新上下文时将主类注册到了容器中,因此获取的变量中也存在主类名。
查看parse()方法:
继续进入:
再次进入:
可以发现该方法会读取ComponentScan的配置信息,接着往下面查看:
我们发现这样的一个方法,该方法返回了一个Bean定义的集合,进入查看:
在该方法中,会对获得的ComponentScan的对象的一些属性进行配置,例如basePackages,我们上面获取的值是null,没有进行配置,使用debug进行查看:
其值变成了主类所在的包
进入该方法:
该方法会根据获取的包扫描指定的包,获取需要注册成Bean的对象。
自动装配
继续回到该方法:
往下走:
查看这个getImports方法:
继续查看:
该方法采用递归的形式,获取所有的Import注解的value值,即所有引入的类。
我们回到组件扫描解析方法:
该方法是自动配置的入口,进入:
接着进入:
进入getImports方法:
继续进入:
进入该方法:
该方法想必读过我上一篇文章的同学已经相当熟悉了,就是加载META-INF/spring.factories中的自动配置类。
然后会进行过滤,将重复的类以及根据@Condition注解排除不需要进行自动装配的Bean。
填充容器总结
填充容器前的准备:获取之前准备的环境,并设置到容器中,对容器的其他属性进行设置,并且会将主类注册到容器中
填充容器:对容器的一些属性进行填充设置,并且会解析ComponentScan注解,对其属性进行设置,扫描指定的包,将配置类注册到Bean容器,在该阶段还会进行自动装配,获取Import注解,调用该类的方法,会加载META-INF/spring.factories中的自动配置类,通过@Condition等注解对配置类进行过滤,最后注册到容器中
SpringBoot启动流程
启动流程包含四个阶段:SpringApplication的初始化、环境准备、创建容器以及填充容器。
SpringApplication的初始化:设置SpringApplication的一些属性,会根据指定的类是否存在来获取web服务的应用类型,包括NONE、RACTIVE、SERVLET,然后加载META-INF/spring.factories中的上下文初始化、注册初始化、上下文监听器,最后确定主方法类,将这些属性填充到SpringApplication中。
环境准备:首先会根据在SpringApplication初始化阶段确定的web服务应用类型,加载指定的环境类创建环境,并填充当前的系统环境、JDK环境以及手动编译的配置文件application.yaml等环境信息,填充到环境中。
创建容器:根据在SpringApplication初始化阶段确定的web服务应用类型,加载指定的容器类创建容器。
填充容器:填充容器包括填充容器前的准备和填充容器,在填充容器前的准备中会将前面进行环境准备的环境,设置到容器中,并填充容器的一些属性,还会将主类注册到容器中。在填充容器阶段会填充容器的一些其他属性,并且会解析ComponentScan注解,扫描指定包将配置类注册到容器中,并且会进行自动装配,将一些第三方jar包中需要进行自动装配的类注册到容器中