一、springboot啟動(dòng)原理及相關(guān)流程概覽
springboot是基于spring的新型的輕量級(jí)框架碉钠,最厲害的地方當(dāng)屬自動(dòng)配置甫贯。那我們就可以根據(jù)啟動(dòng)流程和相關(guān)原理來(lái)看看,如何實(shí)現(xiàn)傳奇的自動(dòng)配置
二赏表、springboot的啟動(dòng)類入口
用過(guò)springboot的技術(shù)人員很顯而易見的兩者之間的差別就是視覺(jué)上很直觀的:springboot有自己獨(dú)立的啟動(dòng)類(獨(dú)立程序)
@SpringBootApplication
public?class?Application {
????public?static?void?main(String[] args) {
????????SpringApplication.run(Application.class, args);
????}
}
?從上面代碼可以看出,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼,所以要揭開SpringBoot的神秘面紗瓢剿,我們要從這兩位開始就可以了岁诉。
三、單單是SpringBootApplication接口用到了這些注解
@Target(ElementType.TYPE)?// 注解的適用范圍跋选,其中TYPE用于描述類、接口(包括包注解類型)或enum聲明
@Retention(RetentionPolicy.RUNTIME)?// 注解的生命周期哗蜈,保留到class文件中(三個(gè)生命周期)
@Documented?// 表明這個(gè)注解應(yīng)該被javadoc記錄
@Inherited?// 子類可以繼承該注解
@SpringBootConfiguration?// 繼承了Configuration前标,表示當(dāng)前是注解類
@EnableAutoConfiguration?// 開啟springboot的注解功能,springboot的四大神器之一距潘,其借助@import的幫助
@ComponentScan(excludeFilters = {?// 掃描路徑設(shè)置(具體使用待確認(rèn))
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public?@interface?SpringBootApplication {
...
}
在其中比較重要的有三個(gè)注解炼列,分別是:
1)@SpringBootConfiguration // 繼承了Configuration,表示當(dāng)前是注解類
2)@EnableAutoConfiguration // 開啟springboot的注解功能音比,springboot的四大神器之一俭尖,其借助@import的幫助
3)@ComponentScan(excludeFilters = { // 掃描路徑設(shè)置(具體使用待確認(rèn))
?接下來(lái)對(duì)三個(gè)注解一一詳解,增加對(duì)springbootApplication的理解
1)@Configuration注解
按照原來(lái)xml配置文件的形式洞翩,在springboot中我們大多用配置類來(lái)解決配置問(wèn)題
配置bean方式的不同:
a)xml配置文件的形式配置bean
<?xml version="1.0"?encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<!--bean定義-->
</beans>
b)javaconfiguration的配置形式配置bean
@Configuration
public?class?MockConfiguration{
????//bean定義
}
注入bean方式的不同:
a)xml配置文件的形式注入bean
<bean id="mockService"?class="..MockServiceImpl">
...
</bean>
b)javaconfiguration的配置形式注入bean
@Configuration
public?class?MockConfiguration{
????@Bean
????public?MockService mockService(){
????????return?new?MockServiceImpl();
????}
}
?任何一個(gè)標(biāo)注了@Bean的方法稽犁,其返回值將作為一個(gè)bean定義注冊(cè)到Spring的IoC容器,方法名將默認(rèn)成該bean定義的id骚亿。
表達(dá)bean之間依賴關(guān)系的不同:
a)xml配置文件的形式表達(dá)依賴關(guān)系
<bean id="mockService"?class="..MockServiceImpl">
<propery name ="dependencyService"?ref="dependencyService"?/>
</bean>
<bean id="dependencyService"?class="DependencyServiceImpl"></bean>
b)javaconfiguration配置的形式表達(dá)依賴關(guān)系
@Configuration
public?class?MockConfiguration{
@Bean
public?MockService mockService(){
???? return?new?MockServiceImpl(dependencyService());
}
@Bean
public?DependencyService dependencyService(){
???? return?new?DependencyServiceImpl();
}
}
如果一個(gè)bean的定義依賴其他bean,則直接調(diào)用對(duì)應(yīng)的JavaConfig類中依賴bean的創(chuàng)建方法就可以了已亥。
2)?@ComponentScan注解
作用:a)對(duì)應(yīng)xml配置中的元素;
b)?ComponentScan的功能其實(shí)就是自動(dòng)掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義;
c)?將這些bean定義加載到IoC容器中.
我們可以通過(guò)basePackages等屬性來(lái)細(xì)粒度的定制@ComponentScan自動(dòng)掃描的范圍来屠,如果不指定虑椎,則默認(rèn)Spring框架實(shí)現(xiàn)會(huì)從聲明@ComponentScan所在類的package進(jìn)行掃描。
1
注:所以SpringBoot的啟動(dòng)類最好是放在root?package下俱笛,因?yàn)槟J(rèn)不指定basePackages捆姜。
3)?@EnableAutoConfiguration
此注解顧名思義是可以自動(dòng)配置,所以應(yīng)該是springboot中最為重要的注解迎膜。
在spring框架中就提供了各種以@Enable開頭的注解泥技,例如:?@EnableScheduling、@EnableCaching星虹、@EnableMBeanExport等零抬;?@EnableAutoConfiguration的理念和做事方式其實(shí)一脈相承簡(jiǎn)單概括一下就是,借助@Import的支持宽涌,收集和注冊(cè)特定場(chǎng)景相關(guān)的bean定義平夜。
@EnableScheduling是通過(guò)@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器【定時(shí)任務(wù)、時(shí)間調(diào)度任務(wù)】
@EnableMBeanExport是通過(guò)@Import將JMX相關(guān)的bean定義加載到IoC容器【監(jiān)控JVM運(yùn)行時(shí)狀態(tài)】
?@EnableAutoConfiguration也是借助@Import的幫助卸亮,將所有符合自動(dòng)配置條件的bean定義加載到IoC容器忽妒。
@EnableAutoConfiguration作為一個(gè)復(fù)合Annotation,其自身定義關(guān)鍵信息如下:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage【重點(diǎn)注解】
@Import(EnableAutoConfigurationImportSelector.class)【重點(diǎn)注解】
public?@interface?EnableAutoConfiguration {
...
}
其中最重要的兩個(gè)注解已經(jīng)標(biāo)注:1、@AutoConfigurationPackage【重點(diǎn)注解】2、@Import(EnableAutoConfigurationImportSelector.class)【重點(diǎn)注解】
??當(dāng)然還有其中比較重要的一個(gè)類就是:EnableAutoConfigurationImportSelector.class
AutoConfigurationPackage注解:
static?class?Registrar?implements?ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public?void?registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
register(registry,?new?PackageImport(metadata).getPackageName());
}
它其實(shí)是注冊(cè)了一個(gè)Bean的定義段直。
new PackageImport(metadata).getPackageName()吃溅,它其實(shí)返回了當(dāng)前主程序類的?同級(jí)以及子級(jí)????的包組件。
以上圖為例鸯檬,DemoApplication是和demo包同級(jí)决侈,但是demo2這個(gè)類是DemoApplication的父級(jí),和example包同級(jí)
也就是說(shuō)喧务,DemoApplication啟動(dòng)加載的Bean中赖歌,并不會(huì)加載demo2,這也就是為什么功茴,我們要把DemoApplication放在項(xiàng)目的最高級(jí)中庐冯。
Import(AutoConfigurationImportSelector.class)注解:
可以從圖中看出??AutoConfigurationImportSelector?繼承了?DeferredImportSelector?繼承了?ImportSelector
ImportSelector有一個(gè)方法為:selectImports。
@Override
public?String[] selectImports(AnnotationMetadata annotationMetadata) {
if?(!isEnabled(annotationMetadata)) {
return?NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return?StringUtils.toStringArray(configurations);
}
可以看到第九行坎穿,它其實(shí)是去加載??public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件展父。這個(gè)外部文件,有很多自動(dòng)配置的類玲昧。如下:
其中栖茉,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector孵延,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器衡载。就像一只“八爪魚”一樣。
自動(dòng)配置幕后英雄:SpringFactoriesLoader詳解
借助于Spring框架原有的一個(gè)工具類:SpringFactoriesLoader的支持隙袁,@EnableAutoConfiguration可以智能的自動(dòng)配置功效才得以大功告成痰娱!
SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置菩收。
public?abstract?class?SpringFactoriesLoader {
//...
public?static?<T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
...
}
public?static?List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
....
}
}
配合@EnableAutoConfiguration使用的話梨睁,它更多是提供一種配置查找的功能支持,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對(duì)應(yīng)的一組@Configuration類
上圖就是從SpringBoot的autoconfigure依賴包中的META-INF/spring.factories配置文件中摘錄的一段內(nèi)容娜饵,可以很好地說(shuō)明問(wèn)題坡贺。
所以,@EnableAutoConfiguration自動(dòng)配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置文件箱舞,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對(duì)應(yīng)的配置項(xiàng)通過(guò)反射(Java Refletion)實(shí)例化為對(duì)應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類遍坟,然后匯總為一個(gè)并加載到IoC容器。
?四晴股、springboot啟動(dòng)流程概覽圖
深入探索SpringApplication執(zhí)行流程
SpringApplication的run方法的實(shí)現(xiàn)是我們本次旅程的主要線路愿伴,該方法的主要流程大體可以歸納如下:
1) 如果我們使用的是SpringApplication的靜態(tài)run方法,那么电湘,這個(gè)方法里面首先要?jiǎng)?chuàng)建一個(gè)SpringApplication對(duì)象實(shí)例隔节,然后調(diào)用這個(gè)創(chuàng)建好的SpringApplication的實(shí)例方法鹅经。在SpringApplication實(shí)例初始化的時(shí)候,它會(huì)提前做幾件事情:
public?static?ConfigurableApplicationContext run(Object[] sources, String[] args) {
????return?new?SpringApplication(sources).run(args);
}
根據(jù)classpath里面是否存在某個(gè)特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來(lái)決定是否應(yīng)該創(chuàng)建一個(gè)為Web應(yīng)用使用的ApplicationContext類型怎诫。
使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationContextInitializer瘾晃。
使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener。
推斷并設(shè)置main方法的定義類幻妓。??
@SuppressWarnings({?"unchecked",?"rawtypes"?})
private?void?initialize(Object[] sources) {
if?(sources !=?null?&& sources.length >?0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2) SpringApplication實(shí)例初始化完成并且完成設(shè)置后蹦误,就開始執(zhí)行run方法的邏輯了,方法執(zhí)行伊始肉津,首先遍歷執(zhí)行所有通過(guò)SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener胖缤。調(diào)用它們的started()方法,告訴這些SpringApplicationRunListener阀圾,“嘿,SpringBoot應(yīng)用要開始執(zhí)行咯狗唉!”初烘。
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);
// 核心點(diǎn):會(huì)打印springboot的啟動(dòng)標(biāo)志,直到server.port端口啟動(dòng)
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context,?null);
stopWatch.stop();
if?(this.logStartupInfo) {
new?StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return?context;
}
catch?(Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw?new?IllegalStateException(ex);
}
}
3) 創(chuàng)建并配置當(dāng)前Spring Boot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile)分俯。
private?ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if?(!this.webEnvironment) {
environment =?new?EnvironmentConverter(getClassLoader()).convertToStandardEnvironmentIfNecessary(environment);
}
return?environment;
}
?4) 遍歷調(diào)用所有SpringApplicationRunListener的environmentPrepared()的方法肾筐,告訴他們:“當(dāng)前SpringBoot應(yīng)用使用的Environment準(zhǔn)備好了咯!”缸剪。
public?void?environmentPrepared(ConfigurableEnvironment environment) {
for?(SpringApplicationRunListener listener :?this.listeners) {
listener.environmentPrepared(environment);
}
}
5) 如果SpringApplication的showBanner屬性被設(shè)置為true吗铐,則打印banner。
private?Banner printBanner(ConfigurableEnvironment environment) {
if?(this.bannerMode == Banner.Mode.OFF) {
return?null;
}
ResourceLoader resourceLoader =?this.resourceLoader !=?null???this.resourceLoader:?new?DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter =?new?SpringApplicationBannerPrinter(resourceLoader,?this.banner);
if?(this.bannerMode == Mode.LOG) {
return?bannerPrinter.print(environment,?this.mainApplicationClass, logger);
}
return?bannerPrinter.print(environment,?this.mainApplicationClass, System.out);
}
?6) 根據(jù)用戶是否明確設(shè)置了applicationContextClass類型以及初始化階段的推斷結(jié)果杏节,決定該為當(dāng)前SpringBoot應(yīng)用創(chuàng)建什么類型的ApplicationContext并創(chuàng)建完成唬渗,然后根據(jù)條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator奋渔,決定是否使用自定義的ResourceLoader镊逝,當(dāng)然,最重要的嫉鲸,將之前準(zhǔn)備好的Environment設(shè)置給創(chuàng)建好的ApplicationContext使用撑蒜。
7) ApplicationContext創(chuàng)建好之后,SpringApplication會(huì)再次借助Spring-FactoriesLoader玄渗,查找并加載classpath中所有可用的ApplicationContext-Initializer座菠,然后遍歷調(diào)用這些ApplicationContextInitializer的initialize(applicationContext)方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理。
@SuppressWarnings({?"rawtypes",?"unchecked"?})
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);
}
}
?8) 遍歷調(diào)用所有SpringApplicationRunListener的contextPrepared()方法藤树。
private?void?prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,<br>ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if?(this.logStartupInfo) {
logStartupInfo(context.getParent() ==?null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);
if?(printedBanner !=?null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources,?"Sources must not be empty");
load(context, sources.toArray(new?Object[sources.size()]));
listeners.contextLoaded(context);
}
?9)最核心的一步浴滴,將之前通過(guò)@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經(jīng)準(zhǔn)備完畢的ApplicationContext。
private?void?prepareAnalyzer(ConfigurableApplicationContext context,FailureAnalyzer analyzer) {
if?(analyzer?instanceof?BeanFactoryAware) {
((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
}
}
?10) 遍歷調(diào)用所有SpringApplicationRunListener的contextLoaded()方法岁钓。
public?void?contextLoaded(ConfigurableApplicationContext context) {
for?(SpringApplicationRunListener listener :?this.listeners) {
listener.contextLoaded(context);
}
}
11)?調(diào)用ApplicationContext的refresh()方法巡莹,完成IoC容器可用的最后一道工序司志。
private?void?refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if?(this.registerShutdownHook) {
try?{
context.registerShutdownHook();
}catch?(AccessControlException ex) {
// Not allowed in some environments.
}
}
}
12) 查找當(dāng)前ApplicationContext中是否注冊(cè)有CommandLineRunner,如果有降宅,則遍歷執(zhí)行它們骂远。
private?void?callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners =?new?ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for?(Object runner :?new?LinkedHashSet<Object>(runners)) {
if?(runner?instanceof?ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if?(runner?instanceof?CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
13) 正常情況下,遍歷執(zhí)行SpringApplicationRunListener的finished()方法腰根、(如果整個(gè)過(guò)程出現(xiàn)異常激才,則依然調(diào)用所有SpringApplicationRunListener的finished()方法,只不過(guò)這種情況下會(huì)將異常信息一并傳入處理)
去除事件通知點(diǎn)后额嘿,整個(gè)流程如下:
public?void?finished(ConfigurableApplicationContext context, Throwable exception) {
for?(SpringApplicationRunListener listener :?this.listeners) {
callFinishedListener(listener, context, exception);
}
}
總結(jié)
到此瘸恼,SpringBoot的核心組件完成了基本的解析,綜合來(lái)看册养,大部分都是Spring框架背后的一些概念和實(shí)踐方式东帅,SpringBoot只是在這些概念和實(shí)踐上對(duì)特定的場(chǎng)景事先進(jìn)行了固化和升華,而也恰恰是這些固化讓我們開發(fā)基于Sping框架的應(yīng)用更加方便高效球拦。
歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 721575865
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用靠闭、高并發(fā)、高性能及分布式坎炼、Jvm性能調(diào)優(yōu)愧膀、Spring源碼,MyBatis谣光,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己檩淋,不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕萄金,使勁拼蟀悦,給未來(lái)的自己一個(gè)交代!