給你一份Spring Boot核心知識清單①-2
由於博客字數限制,不允許發大於20w個字符的文章,所以需要分成兩篇,接上文
五、出神入化:揭秘自動配置原理
典型的Spring Boot應用的啟動類一般均位於 src/main/java
根路徑下,比如 MoonApplication
類:
@SpringBootApplication public class MoonApplication { public static void main(String[] args) { SpringApplication.run(MoonApplication.class, args); } }
其中 @SpringBootApplication
開啟組件掃描和自動配置,而 SpringApplication.run
則負責啟動引導應用程序。 @SpringBootApplication
是一個復合 Annotation
,它將三個有用的註解組合在一起:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { // ...... }
@SpringBootConfiguration
就是 @Configuration
,它是Spring框架的註解,標明該類是一個 JavaConfig
配置類。而 @ComponentScan
啟用組件掃描,前文已經詳細講解過,這裏著重關註 @EnableAutoConfiguration
。
@EnableAutoConfiguration
註解表示開啟Spring Boot自動配置功能,Spring Boot會根據應用的依賴、自定義的bean、classpath下有沒有某個類 等等因素來猜測你需要的bean,然後註冊到IOC容器中。那 @EnableAutoConfiguration
是如何推算出你的需求?首先看下它的定義:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { // ...... }
你的關註點應該在 @Import(EnableAutoConfigurationImportSelector.class)
上了,前文說過, @Import
註解用於導入類,並將這個類作為一個bean的定義註冊到容器中,這裏它將把 EnableAutoConfigurationImportSelector
作為bean註入到容器中,而這個類會將所有符合條件的@Configuration配置都加載到容器中,看看它的代碼:
public String[] selectImports(AnnotationMetadata annotationMetadata) { // 省略了大部分代碼,保留一句核心代碼 // 註意:SpringBoot最近版本中,這句代碼被封裝在一個單獨的方法中 // SpringFactoriesLoader相關知識請參考前文 List<String> factories = new ArrayList<String>(new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader))); }
這個類會掃描所有的jar包,將所有符合條件的@Configuration配置類註入的容器中,何為符合條件,看看 META-INF/spring.factories
的文件內容:
// 來自 org.springframework.boot.autoconfigure下的META-INF/spring.factories // 配置的key = EnableAutoConfiguration,與代碼中一致 org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.....
以 DataSourceAutoConfiguration
為例,看看Spring Boot是如何自動配置的:
@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) public class DataSourceAutoConfiguration { }
分別說一說:
@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class})
:當Classpath中存在DataSource或者EmbeddedDatabaseType類時才啟用這個配置,否則這個配置將被忽略。@EnableConfigurationProperties(DataSourceProperties.class)
:將DataSource的默認配置類註入到IOC容器中,DataSourceproperties定義為:
// 提供對datasource配置信息的支持,所有的配置前綴為:spring.datasource @ConfigurationProperties(prefix = "spring.datasource") public class DataSourceProperties { private ClassLoader classLoader; private Environment environment; private String name = "testdb"; ...... }
@Import({Registrar.class,DataSourcePoolMetadataProvidersConfiguration.class})
:導入其他額外的配置,就以DataSourcePoolMetadataProvidersConfiguration
為例吧。
@Configuration public class DataSourcePoolMetadataProvidersConfiguration { @Configuration @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) static class TomcatDataSourcePoolMetadataProviderConfiguration { @Bean public DataSourcePoolMetadataProvider tomcatPoolDataSourceMetadataProvider() { ..... } } ...... }
DataSourcePoolMetadataProvidersConfiguration是數據庫連接池提供者的一個配置類,即Classpath中存在 org.apache.tomcat.jdbc.pool.DataSource.class
,則使用tomcat-jdbc連接池,如果Classpath中存在 HikariDataSource.class
則使用Hikari連接池。
這裏僅描述了DataSourceAutoConfiguration的冰山一角,但足以說明Spring Boot如何利用條件話配置來實現自動配置的。回顧一下, @EnableAutoConfiguration
中導入了EnableAutoConfigurationImportSelector類,而這個類的 selectImports()
通過SpringFactoriesLoader得到了大量的配置類,而每一個配置類則根據條件化配置來做出決策,以實現自動配置。
整個流程很清晰,但漏了一個大問題: EnableAutoConfigurationImportSelector.selectImports()
是何時執行的?其實這個方法會在容器啟動過程中執行: AbstractApplicationContext.refresh()
,更多的細節在下一小節中說明。
六、啟動引導:Spring Boot應用啟動的秘密
6.1 SpringApplication初始化
SpringBoot整個啟動流程分為兩個步驟:初始化一個SpringApplication對象、執行該對象的run方法。看下SpringApplication的初始化流程,SpringApplication的構造方法中調用initialize(Object[] sources)方法,其代碼如下:
private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } // 判斷是否是Web項目 this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 找到入口類 this.mainApplicationClass = deduceMainApplicationClass(); }
初始化流程中最重要的就是通過SpringFactoriesLoader找到 spring.factories
文件中配置的 ApplicationContextInitializer
和 ApplicationListener
兩個接口的實現類名稱,以便後期構造相應的實例。 ApplicationContextInitializer
的主要目的是在 ConfigurableApplicationContext
做refresh之前,對ConfigurableApplicationContext實例做進一步的設置或處理。ConfigurableApplicationContext繼承自ApplicationContext,其主要提供了對ApplicationContext進行設置的能力。
實現一個ApplicationContextInitializer非常簡單,因為它只有一個方法,但大多數情況下我們沒有必要自定義一個ApplicationContextInitializer,即便是Spring Boot框架,它默認也只是註冊了兩個實現,畢竟Spring的容器已經非常成熟和穩定,你沒有必要來改變它。
而 ApplicationListener
的目的就沒什麽好說的了,它是Spring框架對Java事件監聽機制的一種框架實現,具體內容在前文Spring事件監聽機制這個小節有詳細講解。這裏主要說說,如果你想為Spring Boot應用添加監聽器,該如何實現?
Spring Boot提供兩種方式來添加自定義監聽器:
通過
SpringApplication.addListeners(ApplicationListener<?>...listeners)
或者SpringApplication.setListeners(Collection<?extendsApplicationListener<?>>listeners)
兩個方法來添加一個或者多個自定義監聽器既然SpringApplication的初始化流程中已經從
spring.factories
中獲取到ApplicationListener
的實現類,那麽我們直接在自己的jar包的META-INF/spring.factories
文件中新增配置即可:
org.springframework.context.ApplicationListener=cn.moondev.listeners.xxxxListener\
關於SpringApplication的初始化,我們就說這麽多。
6.2 Spring Boot啟動流程
Spring Boot應用的整個啟動流程都封裝在SpringApplication.run方法中,其整個流程真的是太長太長了,但本質上就是在Spring容器啟動的基礎上做了大量的擴展,按照這個思路來看看源碼:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); // ① SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { // ② ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); // ③ Banner printedBanner = printBanner(environment); // ④ context = createApplicationContext(); // ⑤ analyzers = new FailureAnalyzers(context); // ⑥ prepareContext(context, environment, listeners, applicationArguments,printedBanner); // ⑦ refreshContext(context); // ⑧ afterRefresh(context, applicationArguments); // ⑨ listeners.finished(context, null); stopWatch.stop(); return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } }
① 通過SpringFactoriesLoader查找並加載所有的 SpringApplicationRunListeners
,通過調用starting()方法通知所有的SpringApplicationRunListeners:應用開始啟動了。SpringApplicationRunListeners其本質上就是一個事件發布者,它在SpringBoot應用啟動的不同時間點發布不同應用事件類型(ApplicationEvent),如果有哪些事件監聽者(ApplicationListener)對這些事件感興趣,則可以接收並且處理。還記得初始化流程中,SpringApplication加載了一系列ApplicationListener嗎?這個啟動流程中沒有發現有發布事件的代碼,其實都已經在SpringApplicationRunListeners這兒實現了。
簡單的分析一下其實現流程,首先看下SpringApplicationRunListener的源碼:
public interface SpringApplicationRunListener { // 運行run方法時立即調用此方法,可以用戶非常早期的初始化工作 void starting(); // Environment準備好後,並且ApplicationContext創建之前調用 void environmentPrepared(ConfigurableEnvironment environment); // ApplicationContext創建好後立即調用 void contextPrepared(ConfigurableApplicationContext context); // ApplicationContext加載完成,在refresh之前調用 void contextLoaded(ConfigurableApplicationContext context); // 當run方法結束之前調用 void finished(ConfigurableApplicationContext context, Throwable exception); }
SpringApplicationRunListener只有一個實現類: EventPublishingRunListener
。①處的代碼只會獲取到一個EventPublishingRunListener的實例,我們來看看starting()方法的內容:
public void starting() { // 發布一個ApplicationStartedEvent this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args)); }
順著這個邏輯,你可以在②處的 prepareEnvironment()
方法的源碼中找到 listeners.environmentPrepared(environment);
即SpringApplicationRunListener接口的第二個方法,那不出你所料, environmentPrepared()
又發布了另外一個事件 ApplicationEnvironmentPreparedEvent
。接下來會發生什麽,就不用我多說了吧。
② 創建並配置當前應用將要使用的 Environment
,Environment用於描述應用程序當前的運行環境,其抽象了兩個方面的內容:配置文件(profile)和屬性(properties),開發經驗豐富的同學對這兩個東西一定不會陌生:不同的環境(eg:生產環境、預發布環境)可以使用不同的配置文件,而屬性則可以從配置文件、環境變量、命令行參數等來源獲取。因此,當Environment準備好後,在整個應用的任何時候,都可以從Environment中獲取資源。
總結起來,②處的兩句代碼,主要完成以下幾件事:
判斷Environment是否存在,不存在就創建(如果是web項目就創建
StandardServletEnvironment
,否則創建StandardEnvironment
)配置Environment:配置profile以及properties
調用SpringApplicationRunListener的
environmentPrepared()
方法,通知事件監聽者:應用的Environment已經準備好
③、SpringBoot應用在啟動時會輸出這樣的東西:
. ____ _ __ _ _ /\\ / ___‘_ __ _ _(_)_ __ __ _ \ \ \ ( ( )\___ | ‘_ | ‘_| | ‘_ \/ _` | \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ‘ |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.5.6.RELEASE)
如果想把這個東西改成自己的塗鴉,你可以研究以下Banner的實現,這個任務就留給你們吧。
④、根據是否是web項目,來創建不同的ApplicationContext容器。
⑤、創建一系列 FailureAnalyzer
,創建流程依然是通過SpringFactoriesLoader獲取到所有實現FailureAnalyzer接口的class,然後在創建對應的實例。FailureAnalyzer用於分析故障並提供相關診斷信息。
⑥、初始化ApplicationContext,主要完成以下工作:
將準備好的Environment設置給ApplicationContext
遍歷調用所有的ApplicationContextInitializer的
initialize()
方法來對已經創建好的ApplicationContext進行進一步的處理調用SpringApplicationRunListener的
contextPrepared()
方法,通知所有的監聽者:ApplicationContext已經準備完畢將所有的bean加載到容器中
調用SpringApplicationRunListener的
contextLoaded()
方法,通知所有的監聽者:ApplicationContext已經裝載完畢
⑦、調用ApplicationContext的 refresh()
方法,完成IoC容器可用的最後一道工序。從名字上理解為刷新容器,那何為刷新?就是插手容器的啟動,聯系一下第一小節的內容。那如何刷新呢?且看下面代碼:
// 摘自refresh()方法中一句代碼 invokeBeanFactoryPostProcessors(beanFactory);
看看這個方法的實現:
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); ...... }
獲取到所有的 BeanFactoryPostProcessor
來對容器做一些額外的操作。BeanFactoryPostProcessor允許我們在容器實例化相應對象之前,對註冊到容器的BeanDefinition所保存的信息做一些額外的操作。這裏的getBeanFactoryPostProcessors()方法可以獲取到3個Processor:
ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
不是有那麽多BeanFactoryPostProcessor的實現類,為什麽這兒只有這3個?因為在初始化流程獲取到的各種ApplicationContextInitializer和ApplicationListener中,只有上文3個做了類似於如下操作:
public void initialize(ConfigurableApplicationContext context) { context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks())); }
然後你就可以進入到 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()
方法了,這個方法除了會遍歷上面的3個BeanFactoryPostProcessor處理外,還會獲取類型為 BeanDefinitionRegistryPostProcessor
的bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
,對應的Class為 ConfigurationClassPostProcessor
。 ConfigurationClassPostProcessor
用於解析處理各種註解,包括:@Configuration、@ComponentScan、@Import、@PropertySource、@ImportResource、@Bean。當處理 @import
註解的時候,就會調用<自動配置>這一小節中的 EnableAutoConfigurationImportSelector.selectImports()
來完成自動配置功能。其他的這裏不再多講,如果你有興趣,可以查閱參考資料6。
⑧、查找當前context中是否註冊有CommandLineRunner和ApplicationRunner,如果有則遍歷執行它們。
⑨、執行所有SpringApplicationRunListener的finished()方法。
這就是Spring Boot的整個啟動流程,其核心就是在Spring容器初始化並啟動的基礎上加入各種擴展點,這些擴展點包括:ApplicationContextInitializer、ApplicationListener以及各種BeanFactoryPostProcessor等等。你對整個流程的細節不必太過關註,甚至沒弄明白也沒有關系,你只要理解這些擴展點是在何時如何工作的,能讓它們為你所用即可。
整個啟動流程確實非常復雜,可以查詢參考資料中的部分章節和內容,對照著源碼,多看看,我想最終你都能弄清楚的。言而總之,Spring才是核心,理解清楚Spring容器的啟動流程,那Spring Boot啟動流程就不在話下了。
參考資料
[1] 王福強 著;SpringBoot揭秘:快速構建微服務體系; 機械工業出版社, 2016
[2] 王福強 著;Spring揭秘; 人民郵件出版社, 2009
[3] Craig Walls 著;丁雪豐 譯;Spring Boot實戰;中國工信出版集團 人民郵電出版社,2016
[4] 深入探討 Java 類加載器
[5] spring boot實戰:自動配置原理分析
[6] spring boot實戰:Spring boot Bean加載源碼分析
本文出自 “迷失的月亮” 博客,請務必保留此出處http://luecsc.blog.51cto.com/2219432/1964057
給你一份Spring Boot核心知識清單①-2