前言
最深刻了解一个框架的思想的方式,莫过于看源码,本系列旨在于从Springboot底层源码(Version - 2.6.6)出发,一步步了解springboot是如何运行起来的。
从0-1了解SpringBoot如何运行(一):Environment环境装配
从0-1了解SpringBoot是如何运行起来的(二):定制你的banner
在前述的文章中,我们主要了解了SpringBoot是如何实现环境装配和banner打印的,这一期我们主要来了解SpringBoot是如何创建Context,并对context进行相应的预处理。
public ConfigurableApplicationContext run(String... args) {
......
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
......
}
createApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
createApplicationContext这行代码,主要就是根据当前的当前SpringBoot的webApplicationType进行判断,采用简单工厂
的设计模式来生成相应的context对象。
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
从更详细的代码中可以看出,Context一共有三类,分别对应SERVLET、REACTIVE、DEFAULT三种不同的网络类型。
context.setApplicationStartup(this.applicationStartup);
紧接着一行源码比较简单,就是往对应的Context对象中设置相应的ApplicationStarterUp。
prepareContext
prepareContext方法内的对应源码较多,是本章主要的重点环节。对这个我们逐行进行分析。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将前置加载的配置信息存放到context中。
context.setEnvironment(environment);
// 1、紧接着对Context进行后处理
postProcessApplicationContext(context);
// 2、从当前Spring环境中找到对应的context的初始化器,并进行初始化处理。
applyInitializers(context);
// 3、前置有聊过这个,会发送特定的context事件,从而让相应的监听器处理事件信息,达到解耦。
listeners.contextPrepared(context);
// 4、发送领域事件,关闭相应的bootstrapContex。
bootstrapContext.close(context);
// 5、打印配置信息
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//获取对应的bean工厂,并往其中注册应用的启动参数
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
// 如果这个时候的输出banner不为空,那么页注册进去。
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
//设置context支持循环引用以及 bean定义的重载
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
//如果需要懒加载,那么此时还需要设置相应的后处理器。
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//同时,获取所有的来源文件信息 这里主要指的是启动类。
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//根据相应的sources去进行加载
load(context, sources.toArray(new Object[0]));
//加载完成后发送相应的事件消息。
listeners.contextLoaded(context);
}
postProcessApplicationContext
上述源代码中的第一个重要的点在于postProcessApplicationContext(context)
,具体源代码如下:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
//1、如果当前容器名字的生成器不为空,则往容器工厂中注册容器名字生成器。
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
//2、如果配置的加载器不为空,那么此时需要将配置加载器、类加载器都保存到容器工厂中。
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
//3、如果进行了占位符值的转换,那么此时将相应的转换服务也保存到容器中。
if (this.addConversionService) {
context.getBeanFactory().setConversionService(context.getEnvironment().getConversionService());
}
}
见名知意,这个方法是对Application就行相应的后处理。主要的步骤流程有以下几步:
- 如果当前容器名字生成器不为空,则往
容器工厂
中注册容器名字生成器
。 - 如果配置的加载器不为空,那么此时需要将
配置加载器、类加载器
都保存到容器工厂中。 - 如果进行了
占位符
值的转换,那么此时将转换服务
也保存到容器中。
紧接着,这个时候执行applyInitializers(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);
}
}
该方法的主要原理如下:
1、前置先将Context的初始化器都存储到环境中。
2、然后利用GenericTypeResolver.resolveTypeArgument
方法,判断每个初始化器的初始的context类型。从而筛选出当前用户使用的context模型。
3、第三步,使用筛选出来的context进行初始化。
思考回顾:
很巧的是,我公司的一些业务中也是都采用了Context来包装上下文的请求参数。那么针对于不同业态的context初始化,也可以考虑采用这种方法来增加对于代码的复用性。
contextPrepared
在Context初始化后,会执行 listeners.contextPrepared(context);
代码,这个前置的文章中已经聊过很多次了。就是发送相应的SpringBoot自身定义的领域事件,从而初始化相应内容信息。相似的事件处理还有以下几个。
紧接着,会去发送领域事件,用于关闭当前的BootstrapContext。也就是bootstrapContext.close(context);
这行。我看的时候十分疑惑,这个bootStrapContext是干啥的呢?这里我截取了bootstrapContext中的源码的注释信息。其中有条比较关键的信息:
A simple bootstrap context that is available during startup and Environment post-processing up to the point that the ApplicationContext is prepared.
翻译出来大概是这个意思:一个简单的引导上下文,在启动和环境后处理期间可用,直到准备好ApplicationContext为止。(换句话,bootStrapContext就是个临时产品,等到context初始化完成后就会被删除。)。在删除了bootstrapContext后,会紧接着执行如下代码:
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
主要的内容逻辑如下:
1、判断当前是否需要日志输出启动信息。
2、如果不需要,直接结束;否则需要判断当前context是否属于root,如果属于则需要记录相应的启动日志信息。
3、紧接着会去加载相应的启动配置信息,源代码如下:
protected void logStartupProfileInfo(ConfigurableApplicationContext context) {
Log log = getApplicationLog();
if (log.isInfoEnabled()) {
List<String> activeProfiles = quoteProfiles(context.getEnvironment().getActiveProfiles());
if (ObjectUtils.isEmpty(activeProfiles)) {
List<String> defaultProfiles = quoteProfiles(context.getEnvironment().getDefaultProfiles());
String message = String.format("%s default %s: ", defaultProfiles.size(),
(defaultProfiles.size() <= 1) ? "profile" : "profiles");
log.info("No active profile set, falling back to " + message
+ StringUtils.collectionToDelimitedString(defaultProfiles, ", "));
}else {
String message = (activeProfiles.size() == 1) ? "1 profile is active: "
: activeProfiles.size() + " profiles are active: ";
log.info("The following " + message + StringUtils.collectionToDelimitedString(activeProfiles, ", "));
}
}
}
这段代码主要执行的逻辑简单描述如下:从前置context的配置信息中获取他的激活的环境信息。此时判断是否为空,为空则表示用户没有选择环境信息。否则将用户选择的一个或多个环境配置类型打印到日志中。(其实就是我们日常见到的这句话)
load
输出完日志后,紧跟着的一段代码不太关键,主要是向beanFactory中去注册相应的bean,如系统启动参数的Bean、Banner信息的bean等等。而比较关键的代码是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);
// 注入bean名字生成器、资源加载器、环境变量信息
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();
}
这里的sources主要指的是Application启动类。上述代码会根据context获取对应的容器注册类,也就是BeanDefinitionRegistry,这个是Spring相对比较关键的设计之一。其实通俗来说,BeanDefinition Registry就是一个大型的Map结构,其Key是Bean的名字,value是对应BeanDefinition。这里的``BeanDefinition`是Spring自己定义的一个对象。其关键作用描述如下:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
This is just a minimal interface: The main intention is to allow a BeanFactoryPostProcessor to introspect and modify property values and other bean metadata.
BeanDefinition描述一个bean实例,该实例具有属性值、构造函数参数值和具体实现提供的进一步信息。这只是一个最小的接口:主要目的是允许BeanFactoryPostProcessor内省和修改属性值和其他bean元数据。
大白话来说,BeanDefinition其实就是一个包含所有类信息的定义对象。将其存储在Registry中,是为了方便后续采用反射机制来生成bean,并对相应的Bean进行修改和属性的替换。在创建完bean的加载器,并设置好相应的bean名字生成器
、资源加载器
、环境变量
后。Spring就会调用加载器进行加载了。即执行loader.load();
部分的代码。
loader.load();
的具体源码如下:
void load() {
for (Object source : this.sources) {
load(source);
}
}
首先是会对每个资源都调用相应的load方法。(这里通常只有启动类。)在load方法中,会判断当前资源的类型,根据当前Source类型是类、配置、包还是文本信息,来调用不同的加载方法。
private void load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
load((Class<?>) source);
return;
}
if (source instanceof Resource) {
load((Resource) source);
return;
}
if (source instanceof Package) {
load((Package) source);
return;
}
if (source instanceof CharSequence) {
load((CharSequence) source);
return;
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
对类进行加载的方法源代码如下所示:
private void load(Class<?> source) {
//判断当前该资源类是否是采用grovvy定义的资源类
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// 若是,则采用特定的资源类加载器进行加载。
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
}
//否则判断当前source是否符合1、非匿名类;2、对Grovvy加载关闭;3、拥有无参构造器
if (isEligible(source)) {
//满足以上三点,才能将相应source进行注册。
this.annotatedReader.register(source);
}
}
咱们具体剖析this.annotatedReader.register(source);
这行代码,其源代码如下所示:
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
//新建BeanDefinition
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
//获取bean定义的作用范围:如单例、多例等。
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
// 生成bean的名字信息
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
//设置这个bean的一些属性:如是否需要懒初始化、当前bean是否属于优先加载的等
if (Primary.class == qualifier) {
abd.setPrimary(true);
}else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
//紧接着还需要对bean做一些自定义化的处理.
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
// 将对应的beanDefinition及bean名字组装成新的holder进行处理.简单来说就是对内容进行聚合并提供getter、setter方法信息。
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
//设置当前bean是否需要被aop进行增强代理
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
//最后一步将新的这个bean对象保存到registry中.
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
整段代码比较细致性的流程内容都写成了注释,主要实现的功能就是,新建一个BeanDefinition,并对这个BenaDefition设置作用范围、生成名字、设置懒初始化等一系列操作。最后汇总成Holder并保存到registry中,供后续使用。到此为止,整个Context的预处理的准备工作就全部完成了。
总结:
总的来说,本章内容相对枯燥,主要涉及的内容有如下几个:
1、如何根据不同的启动类型,生产出对应的Context上下文。针对这个问题,Spring采用的解决办法是采用简单工厂的方式,生成对应的Context上下文。
2、紧接着,Spring对相应的Context内容进行了处理,包括如下几个:
- 设置Context的环境配置信息;
- 销毁原有的bootStrapContext;
- 设置context的Bean工厂信息,包括注册启动参数的Bean、设置懒初始化后处理器等;
- 将相应的启动类,注册成相应BeanDefinition保存到registry中,用于后续refresh使用。