我覺得只是運行了個main方法,可卻啓動了服務 —— Java面試必修php
前一章咱們講解了啓動原理的@SpringBootApplication
部分,仔細跟着看代碼仍是很好理解,若需觀看@SpringBootApplication註解部分請點擊,接下來這章主要講解run方法運行部分,run方法運行這裏很枯燥乏味,因此請帶好你的精氣神,準備好水,由於實在是太乾了。css
咱們繼續看這段啓動代碼java
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
分析該方法主要分兩部分,一是SpringApplication
實例化,二是run方法的執行。react
#該方法返回一個ConfigurableApplicationContext對象 SpringApplication.run(MySpringConfigurationApp.class, args);//參數1-應用入口的類,參數2-命令行參數
在SpringApplication實例初始化的時候,它會作 4 件有意義的事情:web
推斷應用類型是Standard仍是Web
可能會出現三種結果REACTIVE
、NONE
、SERVLET
:面試
this.webApplicationType = this.deduceWebApplicationType();//該行代碼位於構造器中
查找並加載全部可用初始化器,設置到initializers
屬性中spring
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));//spring.factories處讀取
找出全部的應用程序監聽器,設置到listeners
屬性中。緩存
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));//該行代碼位於構造器中
推斷並設置main方法的定義類,意思就是找出運行的主類。ruby
this.mainApplicationClass = this.deduceMainApplicationClass();//該行代碼位於構造器中
至此,對於SpringApplication
實例的初始化過程就結束了,接下來進入方法調用環節。bash
接下來的過程比較枯燥無味,若是您跟着仔細看完,你必定有所收穫。
執行流程圖源文件:https://www.processon.com/view/link/5b825917e4b0d4d65be7066aPS 畫圖好累
流程圖對應的代碼以下
public ConfigurableApplicationContext run(String... args) { //計時工具 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); // step1 設置java.awt.headless系統屬性爲true - 沒有圖形化界面 this.configureHeadlessProperty(); //step2 初始化監聽器 SpringApplicationRunListeners listeners = this.getRunListeners(args); //step3 啓動已準備好的監聽器,發佈應用啓動事件 listeners.starting(); Collection exceptionReporters; try { //step4 根據SpringApplicationRunListeners以及參數來裝配環境,如java -jar xx.jar --server.port=8000 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); //step5 打印Banner 就是啓動Spring Boot的時候打印在console上的ASCII藝術字體 Banner printedBanner = this.printBanner(environment); //step6 建立Spring ApplicationContext()上下文 context = this.createApplicationContext(); //step7 準備異常報告器 exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); //step8 裝配Context,上下文前置處理 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); //step9 Spring上下文刷新處理 this.refreshContext(context); //step10 Spring上下文後置結束處理 this.afterRefresh(context, applicationArguments); // 中止計時器監控 stopWatch.stop(); //輸出日誌記錄執行主類名、時間信息 if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } //step11 發佈應用上下文啓動完成事件 listeners.started(context); //step12 執行全部 Runner 運行器 this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { //step13 發佈應用上下文就緒事件 listeners.running(context); //step14 返回應用上下文 return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
this.configureHeadlessProperty();
Headless模式是系統的一種配置模式。設置該默認值爲:true,Java.awt.headless = true
有什麼做用?
對於一個 Java 服務器來講常常要處理一些圖形元素,例如地圖的建立或者圖形和圖表等。這些API基本上老是須要運行一個X-server以便能使用AWT(
Abstract Window Toolkit
,抽象窗口工具集)。然而運行一個沒必要要的 X-server 並非一種好的管理方式。有時你甚至不能運行 X-server,所以最好的方案是運行 headless 服務器,來進行簡單的圖像處理。
方法聲明以下:
private void configureHeadlessProperty() { System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless))); }
SpringApplicationRunListeners listeners = this.getRunListeners(args);
該方法最終落到了loadSpringFactories
方法上,加載了META-INF/spring.factories
這個配置文件下的全部資源,並放入緩存,而後再獲取了org.springframework.context.ApplicationListener
定義的資源列表。這些也就是SpringBoot的自動裝配資源,而後獲取到後存放到list中,並調用createSpringFactoriesInstances
函數建立了SpringFactories的實例。
方法聲明以下:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class[]{SpringApplication.class, String[].class}; return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
META-INF/spring.factories文件部分:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
listeners.starting();
經過step2操做拿到監聽器集合了以後,再統一遍歷出來後,爲每一個Listener都分配了一個任務線程去啓動它們
方法聲明以下: public void starting() { this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); } public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event); Iterator var4 = this.getApplicationListeners(event, type).iterator(); while(var4.hasNext()) { ApplicationListener<?> listener = (ApplicationListener)var4.next(); Executor executor = this.getTaskExecutor(); if (executor != null) { executor.execute(() -> { this.invokeListener(listener, event); }); } else { this.invokeListener(listener, event); } } }
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
咱們來看一下new DefaultApplicationArguments(args)
這個構造函數,跟蹤進去發現調用的SimpleCommandLineArgsParser.parse
函數,從下面代碼中,能夠看到該方法讀取了命令行參數:
方法聲明以下: public CommandLineArgs parse(String... args) { CommandLineArgs commandLineArgs = new CommandLineArgs(); String[] var3 = args; int var4 = args.length; //省略讀取命令行代碼... return commandLineArgs; }
tip:那到這裏你是否會聯想到:java -jar xx.jar --server.port=8000
後面的參數呢
接着咱們看下一行:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
經過這行代碼咱們能夠看到spring boot把前面建立出來的listeners和命令行參數,傳遞到prepareEnvironment
函數中來準備運行環境。來看一下prepareEnvironment
函數的真面目:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = this.getOrCreateEnvironment(); this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared((ConfigurableEnvironment)environment); this.bindToSpringApplication((ConfigurableEnvironment)environment); if (this.webApplicationType == WebApplicationType.NONE) { environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary( (ConfigurableEnvironment)environment); } ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }
在這裏咱們看到了環境是經過getOrCreateEnvironment
建立出來的,再深挖一下getOrCreateEnvironment
的源碼:
private ConfigurableEnvironment getOrCreateEnvironment() { //若是environment 已經存在,則直接返回當前的環境。思考:究竟什麼狀況下回存在呢? if (this.environment != null) { return this.environment; } else { //判斷webApplicationType是否是SERVLET,若是是,則建立Servlet的環境,不然建立基本環境 return (ConfigurableEnvironment)(this.webApplicationType == WebApplicationType.SERVLET ? new StandardServletEnvironment() : new StandardEnvironment()); } }
接着咱們看下一行:
this.configureIgnoreBeanInfo(environment); private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { //若是System中的spring.beaninfo.ignore屬性爲空,就把當前環境中的屬性覆蓋上去 //spring.beaninfo.ignore= true 跳過搜索BeanInfo類 if (System.getProperty("spring.beaninfo.ignore") == null) { Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty("spring.beaninfo.ignore", ignore.toString()); } }
Banner printedBanner = this.printBanner(environment);
spring boot啓動的時候,控制檯顯示的那個藝術字體圖案
context = this.createApplicationContext();
spring boot是根據不一樣的webApplicationType
的類型,來建立不一樣的ApplicationContext
代碼聲明以下: protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch(this.webApplicationType) { case SERVLET: contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"); break; case REACTIVE: contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"); break; default: contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext"); } } catch (ClassNotFoundException var3) { throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3); } } return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }
繼續來看下一步,準備異常報告,又特麼是這個getSpringFactoriesInstances
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
你們能夠再仔細看看META-INF/spring.factories文件
# Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
這行代碼是把上面已經建立好的對象,傳遞給prepareContext來準備上下文
prepareContext()聲明以下: private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //這裏傳入的是 StandardServletEnvironment context.setEnvironment(environment); //設置上下文的beanNameGenerator(bean生成器)和resourceLoader(資源加載器) this.postProcessApplicationContext(context); //拿到以前實例化SpringApplication對象的時候設置的ApplicationContextInitializer,調用它們的initialize方法,對上下文作初始化 this.applyInitializers(context); //調用listeners#contextPrepared,該方法是一個空實現,之後咱們能夠擴展的地方 listeners.contextPrepared(context); //打印啓動日誌 if (this.logStartupInfo) { this.logStartupInfo(context.getParent() == null); this.logStartupProfileInfo(context); } //往上下文的beanFactory中註冊一個singleton的bean,bean的名字是springApplicationArguments,bean的實例是以前實例化的ApplicationArguments對象 context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); //若是以前獲取的printedBanner不爲空 if (printedBanner != null) { //往上下文的beanFactory中註冊一個singleton的bean,bean的名字是springBootBanner,bean的實例就是這個printedBanner.這裏默認是SpringBootBanner. context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } //獲取全部資源 Set<Object> sources = this.getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); //調用load方法註冊啓動類的bean定義,也就是調用SpringApplication.run(Application.class, args);的類 //SpringApplication的load方法內會建立BeanDefinitionLoader的對象,並調用它的load()方法 this.load(context, sources.toArray(new Object[0])); //上下文已經加載,該方法先找到全部的ApplicationListener,遍歷這些listener //若是該listener繼承了ApplicationContextAware類,這一步會調用它的setApplicationContext方法,設置context listeners.contextLoaded(context); }
this.refreshContext(context);
在refreshContext
函數中,第一行調用了refresh(context);
跳轉了一下,下面的代碼是註冊了一個應用關閉的函數鉤子,以下
private void refreshContext(ConfigurableApplicationContext context) { this.refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException var3) { ; } } }
經過代碼跟蹤分析發現,實際上是調用了AbstractApplicationContext
中的refresh方法。
在ServletWebServerApplicationContext和ReactiveWebServerApplicationContext
的refresh函數中都是調用了super.refresh();
代碼以下: public void refresh() throws BeansException, IllegalStateException { Object var1 = this.startupShutdownMonitor; //同步快執行刷新操做 synchronized(this.startupShutdownMonitor) { //準備刷新上下文 this.prepareRefresh(); //獲取了bean工廠之後,設置bean工廠的環境 ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { //發送了一個bean工廠的處理信號,緊接着回調處理器,註冊到bean工廠 this.postProcessBeanFactory(beanFactory); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); //初始化監聽器 this.initMessageSource(); //初始化監聽管理器 this.initApplicationEventMulticaster(); this.onRefresh(); //把spring容器內的listener和beanfactory的listener都添加到廣播器中: this.registerListeners(); //實例化BeanFactory 中已經被註冊可是沒被實例化的全部實例。初始化的過程當中各類BeanPostProcessor已經開始生效 this.finishBeanFactoryInitialization(beanFactory); //收尾,刷新產生的緩存、初始化生命週期處理器LifecycleProcessor,並調用其onRefresh()方法、發佈事件、調用LiveBeansView的registerApplicationContext註冊context。 this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }
在刷新完context後,調用了一個afterRefresh
函數,這個函數前面已經說過了,是爲了給ApplicationContext
的子類留下的一個擴展點
this.afterRefresh(context, applicationArguments);
而後調用了listeners.started(context);
,把監聽器設置成了已經啓動的狀態。
最後調用了callRunners
函數,獲取全部的ApplicationRunner
和CommandLineRunner
而後調用他們的run方法:
把監聽器設置成了已經啓動的狀態
listeners.started(context);
調用了callRunners
函數,獲取全部的ApplicationRunner
和CommandLineRunner
而後調用他們的run方法
listeners.running(context);
觸發全部 SpringApplicationRunListener
監聽器的 running 事件方法。
本文分析了Spring Boot啓動run方法,看得真的是讓人頭痛,但又不得不看,主要分爲如下2步進行分析
SpringApplication實例的構建過程
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都經過META-INF/spring.factories
完成定義。
SpringApplication實例run方法的執行過程
其中主要有一個SpringApplicationRunListeners
的概念,它做爲Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication實例的構建過程當中獲得的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴展性。
若是從可擴展性的角度出發,應用開發者能夠在Spring Boot容器的啓動階段,擴展哪些內容呢:
ApplicationRunner
或者CommandLineRunner
接口的實現類)做者有話說:喜歡的話就請移步Java面試必修網,請自備水,更多幹、幹、乾貨等着你