前言
此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什么事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些什么,那么我建議大家從頭開始一篇一篇按順序讀該系列,不至於從中途插入,看的有些懵懂。當然,文中講的不對的地方也歡迎大家指出,有待改善的地方也希望大家不吝賜教。老規矩:一周至少一更,中途會不定期的更新一些其他的博客,可能是springboot的源碼,也可能是其他的源碼解析,也有可能是其他的。
路漫漫其修遠兮,吾將上下而求索!
github:https://github.com/youzhibing
碼雲(gitee):https://gitee.com/youzhibing
前情回顧
大家還記得上篇博文講了什么嗎,或者說大家知道上篇博文講了什么嗎。這里幫大家做個簡單回顧:
創建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 {@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()); } }
其中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 }
往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); }
我們逐個方法來看
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屬性 }
將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()); } } }
上下文后處理。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); } }
在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 源碼