run方法(四)之prepareContext,絕對有值得你看的地方
前言
此係列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的原始碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的原始碼,不知道springboot在啟動過程中做了些什麼,那麼我建議大家從頭開始一篇一篇按順序讀該系列,不至於從中途插入,看的有些懵懂。當然,文中講的不對的地方也歡迎大家指出,有待改善的地方也希望大家不吝賜教。老規矩:一週至少一更,中途會不定期的更新一些其他的部落格,可能是springboot的原始碼,也可能是其他的原始碼解析,也有可能是其他的。
路漫漫其修遠兮,吾將上下而求索!
前情回顧
大家還記得上篇博文講了什麼嗎,或者說大家知道上篇博文講了什麼嗎。這裡幫大家做個簡單回顧:
建立web應用上下文,對其部分屬性:reader、scanner、beanFactory進行了例項化;reader中例項化了屬性conditionEvaluator;scanner中添加了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean;beanFactory中註冊了8個註解配置處理器的Bean。應用上下文型別實際上是AnnotationConfigServletWebServerApplicationContext,beanFactory的型別是DefaultListableBeanFactory,這兩個型別的類圖大家重點看下,既是上篇博文的重點,也是接下來系列部落格的基點。建立上下文的過程其實還建立了environment,本文中會涉及到environment,大家請留意。
通過createApplicationContext方法之後,context的包含的主要內容如下:
prepareContext
先欣賞下我們的戰績,看看我們對run方法完成了多少的原始碼解讀
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {View Code@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { // 秒錶,用於記錄啟動時間;記錄每個任務的時間,最後會輸出每個任務的總費時 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // spring應用上下文,也就是我們所說的spring根容器 ConfigurableApplicationContext context = null; // 自定義SpringApplication啟動錯誤的回撥介面 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 設定jdk系統屬性java.awt.headless,預設情況為true即開啟 configureHeadlessProperty(); // 獲取啟動時監聽器(EventPublishingRunListener例項) SpringApplicationRunListeners listeners = getRunListeners(args) // 觸發ApplicationStartingEvent事件,啟動監聽器會被呼叫,一共5個監聽器被呼叫,但只有兩個監聽器在此時做了事 listeners.starting(); try { // 引數封裝,也就是在命令列下啟動應用帶的引數,如--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 準備環境:1、載入外部化配置的資源到environment;2、觸發ApplicationEnvironmentPreparedEvent事件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 配置spring.beaninfo.ignore,並新增到名叫systemProperties的PropertySource中;預設為true即開啟 configureIgnoreBeanInfo(environment); // 列印banner圖 Banner printedBanner = printBanner(environment); // 建立應用上下文,並例項化了其三個屬性:reader、scanner和beanFactory context = createApplicationContext(); // 獲取異常報道器,即載入spring.factories中的SpringBootExceptionReporter實現類 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 準備上下文,本文重點 prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
前菜
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
getSpringFactoriesInstances這個方法在之前已經講過,就是載入META-INF/spring.factories中指定型別的bean集合。如下圖
SpringBootExceptionReporter是一個回撥介面,用於支援對SpringApplication啟動錯誤的自定義報告。
先根據SpringBootExceptionReporter獲取FailureAnalyzers的全限定類名,例項化FailureAnalyzers的時候,再次呼叫SpringFactoriesLoader.loadFactoryNames方法獲取型別為FailureAnalyzer的名稱列表,然後再根據名稱列表例項化bean列表。
bean列表建立好之後,設定bean列表中滿足條件的bean的beanFactory和environment,同時也將部分bean應用到context的environment和beanFactory中,程式碼如下
private void prepareFailureAnalyzers(List<FailureAnalyzer> analyzers, ConfigurableApplicationContext context) { for (FailureAnalyzer analyzer : analyzers) { prepareAnalyzer(context, analyzer); } } private void prepareAnalyzer(ConfigurableApplicationContext context, FailureAnalyzer analyzer) { if (analyzer instanceof BeanFactoryAware) { ((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory()); } if (analyzer instanceof EnvironmentAware) { ((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment()); } }View Code
其中NoSuchBeanDefinitionFailureAnalyer bean的setBeanFactory方法
@Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory); this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.metadataReaderFactory = new CachingMetadataReaderFactory( this.beanFactory.getBeanClassLoader()); // Get early as won't be accessible once context has failed to start this.report = ConditionEvaluationReport.get(this.beanFactory); // 往beanFactory中註冊autoConfigurationReport }View Code
往beanFactory中註冊一個名叫autoConfigurationReport的單例bean(型別是ConditionEvaluationReport),這個bean用於後面自動配置條件評估的詳情報告與日誌記錄。
exceptionReporters 獲取成功後,我們來看看beanFactory的變化
正餐
prepareContext內容不多,原始碼如下
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 設定上下文的environment context.setEnvironment(environment); // 應用上下文後處理 postProcessApplicationContext(context); // 在context refresh之前,對其應用ApplicationContextInitializer applyInitializers(context); // 上下文準備(目前是空實現,可用於拓展) listeners.contextPrepared(context); // 列印啟動日誌和啟動應用的Profile if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); // 向beanFactory註冊單例bean:命令列引數bean if (printedBanner != null) { // 向beanFactory註冊單例bean:banner bean context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // Load the sources Set<Object> sources = getAllSources(); // 獲取全部資源,其實就一個:SpringApplication的primarySources屬性 Assert.notEmpty(sources, "Sources must not be empty"); // 斷言資源是否為空 // 將bean載入到應用上下文中 load(context, sources.toArray(new Object[0])); // 向上下文中新增ApplicationListener,並廣播ApplicationPreparedEvent事件 listeners.contextLoaded(context); }View Code
我們逐個方法來看
context.setEnvironment(environment)
/** * {@inheritDoc} * <p> * Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and * {@link ClassPathBeanDefinitionScanner} members. */ @Override public void setEnvironment(ConfigurableEnvironment environment) { super.setEnvironment(environment); // 設定context的environment this.reader.setEnvironment(environment); // 例項化context的reader屬性的conditionEvaluator屬性 this.scanner.setEnvironment(environment); // 設定context的scanner屬性的environment屬性 }View Code
將context中相關的environment全部替換成SpringApplication中建立的environment。還記得這篇中的疑問嗎,引申下就是:之前我們的應用中有兩個environment,一個在context中,一個在SpringApplication中。經過此方法後,就只會存在SpringApplication中的environment了,而context中的原environment會被回收。
postProcessApplicationContext(context);
/** * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can * apply additional processing as required. * @param context the application 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()); } } }View Code
上下文後處理。SpringApplication子類可以根據需要應用其他處理。
由於當前SpringApplication例項的屬性:beanNameGenerator和resourceLoader都為null,所以此方法目前相當於什麼也沒做。此方法可能是我們定製SpringApplication所用。
applyInitializers(context);
/** * Apply any {@link ApplicationContextInitializer}s to the context before it is * refreshed. * @param context the configured ApplicationContext (not refreshed yet) * @see ConfigurableApplicationContext#refresh() */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { // 解析當前initializer實現的ApplicationContextInitializer的泛型引數 Class<?> requiredType = GenericTypeResolver.resolveTypeArgument( initializer.getClass(), ApplicationContextInitializer.class); // 斷言context是否是requiredType的例項 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); // 向context應用初始化器 initializer.initialize(context); } }View Code
在context refresh之前應用ApplicationContextInitializer到context中。還記得SpringApplication的屬性initializers嗎,不記得的可以點這裡。
一共6個initializer,他們的initialize方法都被呼叫,原始碼就不跟了,上圖中已經進行了展示,我們總結下
DelegatingApplicationContextInitializer
environment沒有context.initializer.classes配置項,所以相當於沒有做任何事。
如果配置了context.initializer.classes,獲取其值(逗號分隔的initializer列表字串),轉換成class列表,根據classes列表進行例項化獲取initializer例項列表,再對每個initializer例項呼叫initialize方法。
DelegatingApplicationContextInitializer相當於context.initializer.classes的代理,最終還是會執行到被代理的initializer的initialize方法。 ContextIdApplicationContextInitializer
設定application id:從environment中獲取spring.application.name配置項的值,並把設定成application id,若沒有配置spring.application.name,則取預設值application;
將application id封裝成ContextId物件,註冊到beanFactory中。
ConfigurationWarningsApplicationContextInitializer
向上下文註冊了一個BeanFactoryPostProcessor:ConfigurationWarningsPostProcessor例項;
例項化ConfigurationWarningsPostProcessor的時候,也例項化了它的屬性Check[] checks,check中只有一個型別是ComponentScanPackageCheck的例項。 ServerPortInfoApplicationContextInitializer
向上下文註冊了一個ApplicationListener:ServerPortInfoApplicationContextInitializer物件自己;
ServerPortInfoApplicationContextInitializer實現了ApplicationListener<WebServerInitializedEvent>,所以他本身就是一個ApplicationListener。 SharedMetadataReaderFactoryContextInitializer
向context註冊了一個BeanFactoryPostProcessor:CachingMetadataReaderFactoryPostProcessor例項。 ConditionEvaluationReportLoggingListener
將上下文賦值給自己的屬性applicationContext;
向上下文註冊了一個ApplicationListener:ConditionEvaluationReportListener例項;
從beanFactory中獲取名為autoConfigurationReport的bean賦值給自己的屬性report。
listeners.contextPrepared(context);
還記得SpringApplicationRunListeners中listeners屬性嗎,沒錯,裡面就一個EventPublishingRunListener物件。
呼叫EventPublishingRunListener的contextPrepared,發現其是空實現。
也就是相當於啥事也沒做。
load(context, sources.toArray(new Object[0]));
建立了一個BeanDefinitionLoader物件;BeanDefinitionLoader作為AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader和ClassPathBeanDefinitionScanner的門面,從底層源載入bean定義,包括XML和JavaConfig;
能被載入的source型別包括:Class、Resource、Package和CharSequence四種,每種型別的載入方式也不一樣,Class用AnnotatedBeanDefinitionReader處理、Resource用XmlBeanDefinitionReader處理、Package用ClassPathBeanDefinitionScanner,而CharSequence則比較特殊了,它按Class、Resource、Package的順序處理,哪種處理成功就按哪種處理(CharSequence方式貌似很少用,反正我還沒用過);
而目前我們的source只有一個:class com.lee.shiro.ShiroApplication,是class型別;先判斷ShiroApplication是否有被component註解修飾,很顯然是(SpringBootApplication註解中包含component註解),那麼AnnotatedBeanDefinitionReader來處理:將com.lee.shiro.ShiroApplication封裝成一個名叫ShiroApplication的BeanDefinition物件,並將其註冊到了beanFactory的BeanDefinitionMap中。
listeners.contextLoaded(context);
還記得SpringApplication的屬性listeners嗎,不記得的可以點這裡。將這些ApplicationListener註冊到了上下文中,具體包括ConfigFileApplicationListener,AnsiOutputApplicationListener,LoggingApplicationListener,ClasspathLoggingApplicationListener,BackgroundPreinitializer,DelegatingApplicationListener,ParentContextCloserApplicationListener(實現了ApplicationContextAware介面;將上下文賦值給了屬性context,相當於有了上下文的引用),ClearCachesApplicationListener,FileEncodingApplicationListener,LiquibaseServiceLocatorApplicationListener,EnableEncryptablePropertiesBeanFactoryPostProcessor。
廣播ApplicationPreparedEvent事件,並觸發對應的事件。過濾出匹配事件的監聽器可以檢視這裡,一共過濾出5個監聽器,他們的onApplicationEvent方法會被呼叫,具體做了如下事情:
ConfigFileApplicationListener
向context註冊了一個BeanFactoryPostProcessor:PropertySourceOrderingPostProcessor例項;該例項後面會對我們的property sources進行重排序,另外該例項擁有上下文的引用。
LoggingApplicationListener
向beanFactory中註冊了一個名叫springBootLoggingSystem的單例bean,也就是我們的日誌系統bean。
BackgroundPreinitializer
目前什麼也沒做
DelegatingApplicationListener
目前什麼也沒做
EnableEncryptablePropertiesBeanFactoryPostProcessor
僅僅列印了一句debug日誌,相當於什麼也沒做
甜點
一開始還以為本文內容不會多,但分析分析著,發現內容不少。不管我們是吃撐了還是沒吃飽,都來點甜點收尾。
一般一個單例物件註冊到beanFactory中,beanFactory會有2個屬性都新增此單例物件資訊:singletonObjects、registeredSingletons
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(),key是bean name,value是單例物件
Set<String> registeredSingletons = new LinkedHashSet<>(),存放的是bean name
一般一個bean定義註冊到beanFactory中是,beanFactory也會有2個屬相會新增此bean定義資訊:beanDefinitionMap、beanDefinitionNames
List<String> beanDefinitionNames = new ArrayList<>(),beanDefinition的名稱列表 Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(),key是beanDefinition的名稱,value是beanDefinition物件
另外beanFactory中Set<String> manualSingletonNames = new LinkedHashSet<>,按註冊順序存放手動註冊的單例的名稱。
load方法,我會放到另一篇博文中重點分析;load負責載入bean定義資源,應該是挺重要的,而本文卻講的比較粗糙,我們一起期待吧。
有時候,不是對手有多強大,只是我們不敢去嘗試;勇敢踏出第一步,你會發現自己比想象中更優秀!誠如海因斯第一次跑進人類10s大關時所說:上帝啊,原來那扇門是虛掩著的!
總結
1、上文中的load
就是載入bean定義資源,支援4種方式:Class、Resource、Package和CharSequence。
Class:註解形式的Bean定義;AnnotatedBeanDefinitionReader負責處理。
Resource:一般而言指的是xml bean配置檔案,也就是我們在spring中常用的xml配置。xml的載入大家可以去閱讀《Spring原始碼深度解析》。說的簡單點就是:將xml的bean定義封裝成BeanDefinition並註冊到beanFactory的BeanDefinitionMap中;XmlBeanDefinitionReader負責處理。
Package:以掃包的方式掃描bean定義; ClassPathBeanDefinitionScanner負責處理。
CharSequence:以先後順序進行匹配Class、Resource或Package進行載入,誰匹配上了就用誰的處理方式處理。
當然還支援Groovy形式的Bean定義,有興趣的朋友可以自行去跟下原始碼。
springboot鼓勵用java類實現java bean定義,所以springboot應用中,我們一般只需要關注Class方式、Package方式即可。
2、prepareContext到底做了什麼
1、將context中的environment替換成SpringApplication中建立的environment 2、將SpringApplication中的initializers應用到context中 設定application id,並將application id封裝成ContextId物件,註冊到beanFactory中 向context的beanFactoryPostProcessors中註冊了一個ConfigurationWarningsPostProcessor例項 向context的applicationListeners中註冊了一個ServerPortInfoApplicationContextInitializer例項 向context的beanFactoryPostProcessors中註冊了一個CachingMetadataReaderFactoryPostProcessor例項 向context的applicationListeners中註冊了一個ConditionEvaluationReportListener例項
3、載入兩個單例bean到beanFactory中
向beanFactory中註冊了一個名叫springApplicationArguments的單例bean,該bean封裝了我們的命令列引數; 向beanFactory中註冊了一個名叫springBootBanner的單例bean。
4、載入bean定義資源 資原始檔只有SpringApplication的primarySources集合,裡面就一個資源類:com.lee.shiro.ShiroApplication; 將該資源封裝成了名叫ShiroApplication的BeanDefinition物件,並將其註冊到了beanFactory的BeanDefinitionMap中。 5、將SpringApplication中的listeners註冊到context中,並廣播ApplicationPreparedEvent事件 總共11個ApplicationListener註冊到了context的applicationListeners中; ApplicationPreparedEvent事件的監聽器一共做了兩件事 向context的beanFactoryPostProcessors中註冊了一個PropertySourceOrderingPostProcessor例項 向beanFactory中註冊了一個名叫springBootLoggingSystem的單例bean,也就是我們的日誌系統bean
context中主要是三個屬性增加了內容:beanFactory、beanFactoryPostProcessors和applicationListeners,到目前為止,context的內容如下
參考
《Spring原始碼深度解析》
Spring boot 原始碼