SpringBoot進(jìn)階(叁):Spring Boot啟動(dòng)過(guò)程分析

      網(wǎng)友投稿 913 2025-03-31

      首先貼一張很不錯(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 exceptionReporters = new ArrayList<>(); configureHeadlessProperty();//開(kāi)啟設(shè)置,讓系統(tǒng)模擬不存在io設(shè)備,略。。 SpringApplicationRunListeners listeners = getRunListeners(args);//初始化- listeners.starting(); ... private SpringApplicationRunListeners getRunListeners(String[] args) { Class[] types = new Class[] { SpringApplication.class, String[].class };//SpringApplicationRunListener的構(gòu)造函數(shù)參數(shù)類型 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } private Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader();//從當(dāng)前線程獲取類加載器 //Spring的類加載工具會(huì)從注冊(cè)文件META-INF/spring.factories用指定的類加載器加載類,這里返回相應(yīng)類型的實(shí)現(xiàn)類全限定名 Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);//實(shí)例化 //Spring的排序工具,對(duì)繼承了Ordered接口或者@Priority標(biāo)記的類進(jìn)行排序 AnnotationAwareOrderComparator.sort(instances); return instances; }

      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 sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0]));//加載sources 到上下文中 listeners.contextLoaded(context);//發(fā)布ApplicationPreparedEvent事件 }

      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

      SpringBoot進(jìn)階(叁):Spring Boot啟動(dòng)過(guò)程分析

      回到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)容。

      上一篇:U盤/移動(dòng)硬盤里恢復(fù)出來(lái)的數(shù)據(jù),所有都無(wú)法打開(kāi)?(u盤變成移動(dòng)磁盤并且打開(kāi)不了)
      下一篇:教學(xué)組織流程圖制作模板(教學(xué)組織流程圖制作模板下載)
      相關(guān)文章
      亚洲综合精品成人| 亚洲综合小说另类图片动图 | 国产亚洲精品美女久久久| 亚洲免费视频一区二区三区| 国产成人久久精品亚洲小说| 亚洲av午夜电影在线观看| 亚洲JIZZJIZZ妇女| 亚洲AV无码一区二区乱子仑| 亚洲爆乳少妇无码激情| 亚洲精品无码mⅴ在线观看| 亚洲爆乳成av人在线视菜奈实| 亚洲AV无码国产一区二区三区| 亚洲国产精品99久久久久久| 亚洲av成本人无码网站| 国产91成人精品亚洲精品| 无码不卡亚洲成?人片| 亚洲国产中文字幕在线观看| 红杏亚洲影院一区二区三区| 亚洲性猛交XXXX| 亚洲成人在线网站| 亚洲最大视频网站| 亚洲xxxx18| 国产精品亚洲精品久久精品| 亚洲国产精品专区在线观看| 最新国产AV无码专区亚洲 | 亚洲乱亚洲乱妇无码麻豆| 亚洲av无码一区二区乱子伦as | 777亚洲精品乱码久久久久久 | 亚洲av极品无码专区在线观看| 亚洲自国产拍揄拍| 亚洲最大天堂无码精品区| 国产AV无码专区亚洲AV蜜芽| 亚洲国产精品人人做人人爽| 自拍偷自拍亚洲精品第1页| 亚洲国产精品成人精品无码区| 日韩亚洲AV无码一区二区不卡| 亚洲色图古典武侠| 中文字幕无码精品亚洲资源网久久| jizzjizz亚洲日本少妇| 久久精品夜色噜噜亚洲A∨| 久久久久久a亚洲欧洲AV|