接着上篇繼續分析 SpringBoot 的啓動過程。java
SpringBoot的版本爲:2.1.0 release,最新版本。spring
同樣的,咱們先把時序圖貼上來,方便理解:數組
回顧一下,前面咱們分析到了下面這步:bash
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { // 前面分析了前一部分,SpringApplication的構造方法,本文分析後一部分的run()方法 return new SpringApplication(primarySources).run(args); }
SpringApplication#run方法的內容較多,準備刷屏了:服務器
public ConfigurableApplicationContext run(String... args) { // StopWatch是Spring中一個任務執行時間控制的類,記錄了任務的執行時間 StopWatch stopWatch = new StopWatch(); stopWatch.start();// 開始時間 ConfigurableApplicationContext context = null; // 建立一個SpringBootExceptionReporter的List,記錄啓動過程當中的異常信息 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 設置系統屬性 "java.awt.headless" 爲 true configureHeadlessProperty(); // 獲取全部啓動監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); // 調用全部監聽器的starting() listeners.starting(); try { // 根據入參建立 ApplicationArguments 對象 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 準備應用上下文 environment 對象 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 設置系統屬性 "spring.beaninfo.ignore",是否忽略bean信息 configureIgnoreBeanInfo(environment); // 獲取打印 Banner 對象,也就是應用啓動時候看到控制檯輸出的那個很大的 "SpringBoot" Banner printedBanner = printBanner(environment); // 建立IOC容器 context = createApplicationContext(); // 獲取異常報告,在異常時報告異常信息 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); } // 調用全部監聽器的started(ctx) listeners.started(context); // 調用實現了 ApplicationRunner、CommandLineRunner 的對象的 run 方法 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context);// 調用全部監聽器的running() } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;// 返回上下文 }
上面從大致上介紹了SpringApplication在run()中作的事情,下面詳細分析每步具體的操做。app
SpringApplication#getRunListeners,獲取全部啓動監聽器,同樣的套路,經過SPI機制,經過工廠建立SpringApplicationRunListener的實例:less
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; // 注意這個地方,咱們第二次看到了getSpringFactoriesInstances這個方法,做用就是從類路徑下 META-INF/spring.factories 下加載 SpringFactory 實例,最後傳入 SpringApplicationRunListener.class,建立實例 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }
從 spring.factories 裏面找到以下內容:也就是最後拿到SpringApplicationRunListener的實例是EventPublishingRunListener的對象。源碼分析
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
SpringApplication#configureHeadlessProperty,設置系統屬性 "java.awt.headless" 。headless是系統的一種配置模式,在系統可能缺乏顯示設備、鍵盤或鼠標這些外設的狀況下可使用該模式,也就是告訴服務器,沒有這些硬件設施,當須要這是設備信息的時候,別慌,我可使用awt組件經過計算模擬出這些外設的特性。post
private void configureHeadlessProperty() { // this.headless在初始化的時候值爲 "true" System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty( SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }
SpringApplication#prepareEnvironment,準備應用上下文 environment 對象,像設置當前使用的配置文件profile,就在該方法內完成:網站
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 添加 ConversionService 轉換器,保存啓動參數 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 調用全部監聽器的environmentPrepared(env) listeners.environmentPrepared(environment); // 將 environment 綁定到當前Spring上下文 bindToSpringApplication(environment); // 判斷是否用戶自定義Environment if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
SpringApplication#configureEnvironment,添加 ConversionService 轉換器,保存啓動參數,即最外面main的入參args數組:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService .getSharedInstance(); environment.setConversionService( (ConfigurableConversionService) conversionService); } // 合併命令行啓動參數到當前 enviroment,這個args就是最外層App啓動傳入的main方法的參數 configurePropertySources(environment, args); // 設置當前運行環境的配置文件(dev/uat/prod) configureProfiles(environment, args); }
SpringApplication#configureProfiles ,設置當前運行環境的配置文件,會去查看"spring.profiles.active"中指定的是什麼:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { // 去找當前激活的Profile是哪一個 "spring.profiles.active" environment.getActiveProfiles(); // ensure they are initialized // But these ones should go first (last wins in a property key clash) Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
不當心有陷入進去了,再次回到run()。
接着看後面是獲取Banner。根據環境找到Banner,默認找classpath下面的 banner.txt,Gitee上面不少管理系統打印出各類自定義名稱。實現很簡單,只要你提早製做好banner.txt,放到resources下面,啓動的時候,就會加載你的,覆蓋原始的Banner信息。這部分代碼比較簡單,只要跟進去看下就能看明白,考慮篇幅問題,省略過了。分享一個在線設計Banner.txt的網站。
若是須要關閉Banner輸出,在App裏面調用:
SpringApplication.setBannerMode(Banner.Mode.OFF);// 關掉Banner
接下來重點看一下 SpringApplication#prepareContext,分析SpringBoot如何準備上下文的:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 給當前 context 設置 environment context.setEnvironment(environment); // 設置 beanNameGenerator、resourceLoader、ConversionService postProcessApplicationContext(context); // 獲取全部 ApplicationContextInitializer,調用其 initialize(ctx) 方法 applyInitializers(context); // 調用全部監聽器的contextPrepared(ctx) listeners.contextPrepared(context); if (this.logStartupInfo) { // 打印啓動進程信息 logStartupInfo(context.getParent() == null); logStartupProfileInfo(context);// 打印 profile 信息 } // Add boot specific singleton beans 註冊 SpringBoot 特有的單實例 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { // 設置是否容許重寫 BeanDefinition ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 建立 BeanDefinitionLoader,用於加載 BeanDefinition,此處加載應用啓動類有@SpringBootApplication 註解的主類 load(context, sources.toArray(new Object[0])); // 調用全部監聽器的contextLoaded(ctx) listeners.contextLoaded(context); }
穩住,快啓動完了。接着看SpringApplication#refreshContext,完成刷新上下文的操做:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context);// 刷新上下文,最後使用了AbstractApplicationContext#refresh方法,進入了Spring的啓動流程 if (this.registerShutdownHook) { try { // 註冊中止鉤子,爲了在應用程序shutdown的時候銷燬全部 bean 實例 context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
而後是到了SpringApplication#afterRefresh,這是一個模板方法,父類不提供實現,留給子類發揮想象實現,在context刷新好以後須要作的事情能夠在此方法實現中完成。
最後就是 stopWatch.stop() 中止計時,打印啓動耗時信息,回調監聽器的started(ctx)方法,返回上下文context。
至此,SpringBoot啓動過程分析完成。