SpringBoot進(jìn)階(叁):Spring Boot啟動(dòng)過(guò)程分析
首先貼一張很不錯(cuò)的圖,springboot啟動(dòng)結(jié)構(gòu)圖,圖片出自springboot啟動(dòng)流程解析。
本文的分析基于Spring Boot 2.1.*,非Spring的代碼只有下面這個(gè)啟動(dòng)main函數(shù):
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication application = new SpringApplication(AppServer.class); application.run(args); } }
1
2
3
4
5
6
7
構(gòu)造函數(shù)
SpringApplication的構(gòu)造函數(shù)實(shí)例化了初始化上下文的各種接口–ApplicationContextInitializer以及-–ApplicationListener,要注意的是這里的實(shí)例化,并不像平時(shí)的Spring Components一樣通過(guò)注解和掃包完成,而是通過(guò)一種不依賴Spring上下文的加載方法,這樣才能在Spring完成啟動(dòng)前做各種配置。
Spring的解決方法是以接口的全限定名作為key,實(shí)現(xiàn)類的全限定名作為value記錄在項(xiàng)目的META-INF/spring.factories文件中,然后通過(guò)SpringFactoriesLoader工具類提供靜態(tài)方法進(jìn)行類加載并緩存下來(lái),spring.factories是Spring Boot的核心配置文件,后面會(huì)繼續(xù)說(shuō)明。
另外比較有意思的是兩個(gè)deduce方法,Spring Boot項(xiàng)目主要的目標(biāo)之一就是自動(dòng)化配置,通過(guò)這兩個(gè)deduce方法可以看出,Spring Boot的判斷方法之一是檢查系統(tǒng)中是否存在的核心類。
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath();//通過(guò)核心類判斷是否開(kāi)啟、開(kāi)啟什么web容器 //實(shí)例化初始器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //實(shí)例化- setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
1
2
3
4
5
6
7
8
9
10
11
Run
初始化完成之后就進(jìn)到了run方法,run方法完成了所有Spring的整個(gè)啟動(dòng)過(guò)程:準(zhǔn)備Environment——發(fā)布事件——?jiǎng)?chuàng)建上下文、bean——刷新上下文——結(jié)束,其中穿插了很多-的動(dòng)作,并且很多邏輯都是靠各種-的實(shí)現(xiàn)類執(zhí)行的,所以在分析run方法之前,先看下各種核心-、接口的作用。
ConfigurableApplicationContext
不得不說(shuō),用IDEA分析源碼真的很方便,直接生成接口的UML類圖:
相對(duì)于只讀的ApplicationContext而言,ConfigurableApplicationContext提供了配置上下文的接口,如設(shè)置Environment、-、切面類、關(guān)閉上下文的鉤子等,還有刷新上下文的接口。默認(rèn)是只讀的接口,接口名前面加Configurable對(duì)應(yīng)是一個(gè)提供可配置接口的新接口——在Spring很多配置相關(guān)的接口中都有這樣的繼承形式,例如ConfigurableEnvironment和Environment、ConfigurablePropertyResolver和PropertyResolver、ConfigurableBeanFactory和BeanFactory等等。
繼承的三個(gè)父類接口里,Closeable提供了關(guān)閉時(shí)資源釋放的接口,Lifecycle是提供對(duì)生命周期控制的接口(start\stop)以及查詢當(dāng)前運(yùn)行狀態(tài)的接口,ApplicationContext則是配置上下文的中心配置接口,繼承了其他很多配置接口,其本身提供查詢諸如id、應(yīng)用程序名等上下文檔案信息的只讀接口,以及構(gòu)建自動(dòng)裝配bean的工廠(注釋上官方說(shuō)該接口提供的工廠是用于注冊(cè)上下文外部的bean的,但調(diào)試發(fā)現(xiàn)和在程序內(nèi)@Autowired獲取到的工廠是同一個(gè)對(duì)象…)。簡(jiǎn)單寫下ApplicationContext繼承的父類接口。
EnvironmentCapable 提供Environment接口。
MessageSource 國(guó)際化資源接口。
ApplicationEventPublisher 事件發(fā)布器。
ResourcePatternResolver 資源加載器。
HierarchicalBeanFactory、ListableBeanFactory 這兩個(gè)都繼承了bean容器的根接口BeanFactory,具體在另一篇博客Spring的bean工廠分析分析。
ConfigurableEnvironment 一般在寫業(yè)務(wù)代碼時(shí)使用的都是只讀類型的接口Environment,該接口是對(duì)運(yùn)行程序環(huán)境的抽象,是保存系統(tǒng)配置的中心,而在啟動(dòng)過(guò)程中使用的則是可編輯的ConfigurableEnvironment。接口的UML類圖如下,提供了合并父環(huán)境、添加active profile以及一些設(shè)置解析配置文件方式的接口。
其中一個(gè)比較重要的方法MutablePropertySources getPropertySources();,該方法返回一個(gè)可編輯的PropertySources,如果有在啟動(dòng)階段自定義環(huán)境的PropertySources的需求,就可以通過(guò)該方法設(shè)置。
EventPublishingRunListener
該-實(shí)際上是一個(gè)用于廣播Spring事件的廣播器,實(shí)現(xiàn)SpringApplicationRunListener接口的方法都是包裝一個(gè)Spring事件并進(jìn)行廣播,例如:
@Override public void contextPrepared(ConfigurableApplicationContext context) { this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context)); } @Override public void running(ConfigurableApplicationContext context) { context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context)); }
1
2
3
4
5
6
7
8
可以看到有兩種廣播方式,一種是當(dāng)Spring還在啟動(dòng)的時(shí)候,通過(guò)-內(nèi)部的SimpleApplicationEventMulticaster廣播器進(jìn)行廣播;一種是當(dāng)Spring啟動(dòng)完成內(nèi)部的廣播器可用時(shí),直接調(diào)用上下文提供的接口進(jìn)行廣播。
繼續(xù)分析Run
了解了一些核心的接口后,就可以啟動(dòng)Debug模式運(yùn)行Run方法了,由于涉及的方法調(diào)用很多,以下代碼將拆分源碼,并將方法簽名記在前面。
首先開(kāi)啟了一個(gè)秒表用來(lái)統(tǒng)計(jì)啟動(dòng)時(shí)間并在日志打印(如果開(kāi)啟控制字),聲明了一些在后面需要用到的變量,然后開(kāi)始初始化SpringApplicationRunListener類型的-,SpringApplicationRunListeners對(duì)-List進(jìn)行了封裝,例如調(diào)用.starting()時(shí)會(huì)遍歷內(nèi)部所有-調(diào)用其.starting()方法。
public ConfigurableApplicationContext run(String... args){ StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
調(diào)試發(fā)現(xiàn),注冊(cè)為SpringApplicationRunListener的實(shí)現(xiàn)類只有EventPublishingRunListener,之前說(shuō)過(guò)該注冊(cè)器是一個(gè)用于廣播Spring事件的廣播器,進(jìn)到構(gòu)造函數(shù)中可以看到都有哪些-被綁定到了這個(gè)廣播器中,這里每個(gè)-的作用就不再深入了,需要說(shuō)的是,如果在項(xiàng)目中有什么需要集成到Spring的框架,可以注冊(cè)SpringApplicationRunListener\ApplicationListener的實(shí)現(xiàn)類,監(jiān)聽(tīng)Spring的不同啟動(dòng)事件并執(zhí)行集成的邏輯。當(dāng)然也有別的方法,例如:Creating a Custom Starter with Spring Boot。
繼續(xù)往下看run方法,這里重點(diǎn)是準(zhǔn)備Environment的邏輯。首先Spring會(huì)根據(jù)web容器的類型新建一個(gè)ConfigurableEnvironment,不同的web容器類型的Environment會(huì)重載customizePropertySources方法,該方法會(huì)注入不同的propertySources,例如如果開(kāi)啟內(nèi)嵌的Servlet容器,就會(huì)注入servlet context init params等相關(guān)的參數(shù)。接下來(lái)會(huì)對(duì)新建的Environment執(zhí)行配置寫入的邏輯,主要是把main方法中設(shè)置到SpringApplication的參數(shù)寫入到Environment中,然后發(fā)布ApplicationEnvironmentPreparedEvent事件,做一些綁定后返回Environment。
try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//封裝main方法的參數(shù) //初始化填充Environment的參數(shù) ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); configureIgnoreBeanInfo(environment);//設(shè)置獲取BeanInfo的一個(gè)參數(shù),有興趣的可以去了解下Introspector.getBeanInfo(Class> beanClass, int flags)這個(gè)方法 ... private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { //新建\獲取當(dāng)前Environment實(shí)例 ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs());//配置參數(shù) listeners.environmentPrepared(environment);//發(fā)布事件 bindToSpringApplication(environment);//綁定"spring.main"為當(dāng)前的application,做SpEL用 if (!this.isCustomEnvironment) {//轉(zhuǎn)換environment的類型,但這里應(yīng)該類型和deduce的相同不用轉(zhuǎn)換 environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } //將現(xiàn)有的配置封裝成ConfigurationPropertySourcesPropertySource,看起來(lái)是為了做SpEL的,看不懂~ ConfigurationPropertySources.attach(environment); return environment; } protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) {//默認(rèn)開(kāi)啟,會(huì)注入一組轉(zhuǎn)換工具,例如StringToDurationConverter ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService(ConfigurableConversionService) conversionService); } configurePropertySources(environment, args);//如果main啟動(dòng)時(shí)設(shè)置了默認(rèn)參數(shù)或者有命令行參數(shù),則寫入到environment中 configureProfiles(environment, args);//如果main啟動(dòng)時(shí)設(shè)置了profile,則寫入到environment的ActiveProfiles中 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
繼續(xù)往下看run方法,這里會(huì)創(chuàng)建Spring的上下文實(shí)例,詳情請(qǐng)看另一篇博客Spring Boot Context分析,簡(jiǎn)而言之就是根據(jù)Web容器類型的不同來(lái)創(chuàng)建不用的上下文實(shí)例。
Banner printedBanner = printBanner(environment);//打印標(biāo)語(yǔ) context = createApplicationContext();//創(chuàng)建上下文實(shí)例 //異常播報(bào)器,默認(rèn)有org.springframework.boot.diagnostics.FailureAnalyzers exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); ...
1
2
3
4
5
繼續(xù)往下看run方法,接下來(lái)是對(duì)剛創(chuàng)建的上下文完成加載。加載過(guò)程先填充Environment以及設(shè)置的參數(shù),然后執(zhí)行注冊(cè)到spring.factories的ApplicationContextInitializer切面,如果自己實(shí)現(xiàn)切面的話要注意這時(shí)context已經(jīng)有的信息是什么。接著發(fā)布ApplicationContextInitializedEvent事件,然后加載bean,最后發(fā)布ApplicationPreparedEvent事件。
prepareContext(context, environment, listeners, applicationArguments,printedBanner); ... private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); //如果application有設(shè)置beanNameGenerator、resourceLoader就將其注入到上下文中,并將轉(zhuǎn)換工具也注入到上下文中 postProcessApplicationContext(context); applyInitializers(context);//調(diào)用初始化的切面 listeners.contextPrepared(context);//發(fā)布ApplicationContextInitializedEvent事件 if (this.logStartupInfo) {//日志 logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments);//注入main方法的參數(shù) if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { //如果bean名相同的話是否允許覆蓋,默認(rèn)為false,相同會(huì)拋出異常 ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // 這里獲取到的是BootstrapImportSelectorConfiguration這個(gè)class,而不是自己寫的啟動(dòng)來(lái),這個(gè)class是在之前注冊(cè)的BootstrapApplicationListener的監(jiān)聽(tīng)方法中注入的 Set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
回到run方法,在實(shí)例化上下文并完成相關(guān)配置后,會(huì)刷新上下文。
refreshContext(context); ... AbstractApplicationContext public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //記錄啟動(dòng)時(shí)間、狀態(tài),web容器初始化其property,復(fù)制listener prepareRefresh(); //這里返回的是context的BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //beanFactory注入一些標(biāo)準(zhǔn)組件,例如ApplicationContextAwareProcessor,ClassLoader等 prepareBeanFactory(beanFactory); try { //給實(shí)現(xiàn)類留的一個(gè)鉤子,例如注入BeanPostProcessors,這里是個(gè)空方法 postProcessBeanFactory(beanFactory); // 調(diào)用切面方法 invokeBeanFactoryPostProcessors(beanFactory); // 注冊(cè)切面bean registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // bean工廠注冊(cè)一個(gè)key為applicationEventMulticaster的廣播器 initApplicationEventMulticaster(); // 給實(shí)現(xiàn)類留的一鉤子,可以執(zhí)行其他refresh的工作,這里是個(gè)空方法 onRefresh(); // 將listener注冊(cè)到廣播器中 registerListeners(); // 實(shí)例化未實(shí)例化的bean finishBeanFactoryInitialization(beanFactory); // 清理緩存,注入DefaultLifecycleProcessor,發(fā)布ContextRefreshedEvent finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
回到run方法,最后的邏輯就是發(fā)布啟動(dòng)完成的事件,并調(diào)用監(jiān)聽(tīng)者的方法。
... afterRefresh(context, applicationArguments);//給實(shí)現(xiàn)類留的鉤子,這里是一個(gè)空方法。 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context);//發(fā)布ApplicationStartedEvent事件 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context);//發(fā)布ApplicationReadyEvent事件 } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Spring Spring Boot
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。