前言
上下文Context可以說spring boot中最重要的一個概念,不僅包含了tomcat和spring mvc的啓動和管理,還對spring mvc原有模式中的bean註冊進行了大幅簡化,理解Spring boot的Context可以說是理解spring boot的基礎。
原理分析(八)已經介紹了Spring boot的ApplicationContext的定義實現和創建,其中就包含了上下文Context攜帶了哪些必要的信息,能夠起到哪些作用。本文主要對上下文的準備和刷新進行介紹。
首先還是對齊spring boot啓動總流程中run方法的相對應的信息。在上下文Context構建完成之後,接下來就是對上下文的準備和刷新,如下代碼所示。
......
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
......
上下文準備
prepareContext方法中實現了上下文相關的初始化。具體內容如下注釋。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//1.設置環境
context.setEnvironment(environment);
//2.後處理上下文設置
postProcessApplicationContext(context);
//3.初始化上下文
applyInitializers(context);
//4.事件監聽器發送上下文準備完畢事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//5.bean工廠中註冊應用程序參數bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//6.bean工廠中註冊條幅bean
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 7.注入基礎類
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 8.事件監聽器發送上下文加載完成事件
listeners.contextLoaded(context);
}
-
- 環境設置:上下文中包含了系統的環境相關的信息,主要是profile和properties配置,properties配置不僅包括項目內的properties文件,還有JVM system properties、操作系統環境變量等。
-
- 後處理上下文設置:
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
//如果有,設置bean name生成器
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());
}
}
//設置Converter管理器
if (this.addConversionService) {
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
}
}
默認情況下,這裏是沒有設置bean name生成器和資源加載器,如果需要,可以自己設置。不過,最後的ConverterService是會被設置的。Converter組件是用來做參數轉換的,比如String到日期的轉換等,這些轉換器都由ConversionService管理。
-
- 初始化上下文用到了ApplicationContextInitializer的子類實例:
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);
}
}
如果你還記得,上下文初始化子類的設置是在SpringApplication類的構造函數中完成的。
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
上下文初始化子類,可以在上下文中設置一些必要的屬性。
-
- 4和8一樣,這部分已經在spring boot的事件監聽中講過了。參考原理分析(七)。
-
- bean工廠中註冊應用程序參數bean:將應用main中傳入的參數設置爲bean
-
- bean工廠中註冊條幅bean:spring boot條幅相關的在之前也講過,就是開始打印的很大的spring boot。
-
- 注入基礎類:這裏獲取了基礎類,其中就包括入口類,然後分析了註解並注入到bean中。
上下文刷新
上下文刷新主要流程的邏輯不是在SpringApplication類中實現的。refreshContext中調用了refresh方法,而refresh方法回調了上下文Context的refresh方法。典型的我刷新我自己。
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
由於這裏的實現是ServletWebServerApplicationContext,所以調用的是這個類的refresh方法,再往下,實際上
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
stopAndReleaseWebServer();
throw ex;
}
}
調用的是父類AbstractApplicationContext的refresh,正是在這個refresh方法中,定義了上下文刷新的主要流程步驟。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1.爲上下文刷新作準備
prepareRefresh();
// 2.通知子類去刷新內部的bean工廠
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3.準備bean工廠
prepareBeanFactory(beanFactory);
try {
//4. 允許bean工廠的後處理操作
postProcessBeanFactory(beanFactory);
//5. 觸發bean工廠中註冊的BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
//6. 註冊BeanPostProcessor處理器:.
registerBeanPostProcessors(beanFactory);
//7. 初始化MessageSource
initMessageSource();
//8. 初始化事件監聽管理器
initApplicationEventMulticaster();
//9. 初始化Web服務器等(tomcat和spring mvc)
onRefresh();
//10. 檢查事件監聽處理器並註冊
registerListeners();
//11. 實例化所有最後存在的單例bean
finishBeanFactoryInitialization(beanFactory);
//12. 啓動Web服務器(tomcat和spring mvc)
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
-
- 爲上下文刷新做準備:
主要是做了環境屬性的配置,比如properties屬性讀入後存在一些佔位但未確定的屬性,這時就會被從context中讀出的配置所取代,完成環境配置的最終更新。還會做一些事件監聽器的初始化,這裏的事件監聽器是針對參考原理分析(七)事件的細化處理,比如LoggingApplicationListener就會監聽多個已經講過的事件。
- 爲上下文刷新做準備:
-
- 通知子類去刷新內部的bean工廠:對beanFactory進行刷新並獲取
-
- 準備bean工廠:
獲取Context的相關設置,對beanFactory進行配置。
內部設置了bean的加載器、bean加載後處理器、事件發佈器、環境變量、應用配置等等。
- 準備bean工廠:
-
- 允許bean工廠的後處理操作:
這裏主要設置了使用註解加載的bean的後處理器以及普通bean的後處理器。關於bean的後處理器就是之前提到的BeanPostProcessor。
- 允許bean工廠的後處理操作:
-
- 觸發bean工廠中註冊的BeanFactoryPostProcessor:
BeanFactoryPostProcessor需要和4中的BeanPostProcessor區分開。BeanFactoryPostProcessor能夠在bean創建前修改bean的定義屬性。而BeanPostProcessor是在spring容器加載了bean的定義文件並且實例化bean之後執行的。
- 觸發bean工廠中註冊的BeanFactoryPostProcessor:
-
- 註冊BeanPostProcessor處理器:
這裏代碼上的註釋和實際的內容有些出入,代碼上的註釋是註冊阻止bean創建的處理器,但是源碼裏的實際內容是註冊了各種處理器。可能是我理解有誤,有待確定。
- 註冊BeanPostProcessor處理器:
-
- 初始化MessageSource:
MessageSource是用來處理國際化的問題,這個之前有提到。
- 初始化MessageSource:
-
- 初始化事件監聽管理器:
這裏的事件監聽管理器和後面的10的事件監聽處理器,在原理分析(七)已經有涉及到。
- 初始化事件監聽管理器:
-
- 初始化Web服務器等(tomcat和spring mvc)
這個方法是由子類ServletWebServerApplicationContext實現的
- 初始化Web服務器等(tomcat和spring mvc)
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
這裏createWebServer方法是TomcatServletWebServerFactory.getWebServer方法,
初始化tomcat的容器。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
這裏就涉及到了Tomcat和spring mvc如何初始化的內容了。在我的tomcat + spring mvc原理系列文章中有比較詳細的介紹。
-
- 實例化所有最後存在的單例bean
-
- 啓動Web服務器(tomcat和spring mvc)
調用了tomcat的start方法,整個服務被啓動了起來。
- 啓動Web服務器(tomcat和spring mvc)
附:
spring boot的上下文管理是依託於spring的bean的註冊和管理的基礎上的,所以其中涉及了較多bean工廠和處理器相關的內容。如果想要有更加清楚的認識,需要深入瞭解spring的原理。