目錄html
最近在學習Spring Boot相關的課程,過程當中以筆記的形式記錄下來,方便之後回憶,同時也在這裏和你們探討探討,文章中有漏的或者有補充的、錯誤的都但願你們可以及時提出來,本人在此先謝謝了!java
開始以前呢,但願你們帶着幾個問題去學習:
一、Spring Boot SpringApplication 是什麼?
二、總體流程或結構是怎樣的?
三、重點內容或者核心部分是什麼?
四、怎麼實現的?
五、是怎麼和 Spring 關聯起來的?
這是對自個人提問,我認爲帶着問題去學習,是一種更好的學習方式,有利於加深理解。好了,接下來進入主題。react
上篇文章咱們講了 SpringApplication
的準備階段,在這個階段,完成了運行時所須要準備的資源,如:initializers
、listeners
等。而這篇文章咱們就來說講 SpringApplication
的運行階段,在這個階段,它是如何啓動 Spring
應用上下文的,且如何與 Spring
事件結合起來,造成完整的 SpringApplication
生命週期的。web
注:本篇文章所用到的
Spring Boot
版本是2.1.6.BUILD-SNAPSHOT
spring
上篇文章咱們講了 SpringApplication
的構造方法,這裏咱們就來說講 SpringApplication
的核心,也就是run方法,代碼以下:編程
public class SpringApplication { ... public ConfigurableApplicationContext run(String... args) { // 這是 Spring 的一個計時器,計算代碼的執行時間(ms級別) StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 這倆變量在後面賦值處進行說明 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 用來設置java.awt.headless屬性值 configureHeadlessProperty(); // 該對象屬於組合模式的實現,核心是內部關聯的 SpringApplicationRunListener 集合,SpringApplicationRunListener 是 Spring Boot 的運行時監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); // 會在不一樣的階段調用對應的方法,這裏表示啓動run方法被調用 listeners.starting(); try { // 用來獲取 SpringApplication.run(args)傳入的參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 獲取 properties 配置文件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 設置 spring.beaninfo.ignore 的屬性值,判斷是否跳過搜索BeanInfo類 configureIgnoreBeanInfo(environment); // 這裏是項目啓動時,控制檯打印的 Banner Banner printedBanner = printBanner(environment); // 這裏就是建立 Spring 應用上下文 context = createApplicationContext(); // 獲取 spring.factories 中key爲 SpringBootExceptionReporter 的類名集合 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 這裏是準備 Spring 應用上下文 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 這裏是啓動 Spring 應用上下文,底層調用的是 ApplicationContext 的 refresh() 方法,到這裏就正式進入了 Spring 的生命週期,同時,SpringBoot的自動裝配特性也隨之啓動 refreshContext(context); // 裏面是空的,猜想應該是交由開發人員自行擴展 afterRefresh(context, applicationArguments); stopWatch.stop(); // 這裏打印啓動信息 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // ApplicationContext 啓動時,調用該方法 listeners.started(context); // 項目啓動後,作的一些操做,開發人員可自行擴展 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // ApplicationContext 啓動完成時,調用該方法 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } ... }
上面就是整個過程的概覽,能夠看到,在運行階段執行的操做比較多,雖然看起來雜亂無章,但其實仍是有規律可循的。好比,執行的 SpringApplicationRunListeners
中的階段方法,剛啓動階段的 starting
、已啓動階段的 started
、啓動完成階段的 running
等。還有對應的 Spring
應用上下文的建立、準備、啓動操做等。接下來,就對裏面的幾個核心對象進行討論。springboot
咱們先來看看 SpringApplicationRunListeners
對象,從代碼能夠看出該對象是由 getRunListeners
方法建立的:app
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
能夠看到,經過傳入的 getSpringFactoriesInstances
方法的返回值,執行 SpringApplicationRunListeners
的構造方法,進行對象的建立。接着看 getSpringFactoriesInstances
方法:less
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
看到這你們應該比較熟悉了,經過前面幾篇文章的討論咱們知道,該方法經過 SpringFactoriesLoader.loadFactoryNames
返回全部 classpass 下的 spring.factories
文件中 key 爲 SpringApplicationRunListener
的實現類集合。如 Spring Boot 的內建實現:異步
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
最後,就是將該集合傳入 SpringApplicationRunListeners
的構造方法:
class SpringApplicationRunListeners { ... private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } ... }
裏面是將集合賦值到 listeners
屬性,能夠看到 SpringApplicationRunListeners
屬於組合模式的實現,核心實際上是內部關聯的 SpringApplicationRunListener
對象集合,當外部調用該階段方法時,就會迭代執行集合中 SpringApplicationRunListener
對應的方法。因此接下來咱們就來討論 SpringApplicationRunListener
。
SpringApplicationRunListener
負責在 SpringBoot
的不一樣階段廣播相應的事件,而後調用實際的 ApplicationListener
類,在該類的 onApplicationEvent
方法中,根據不一樣的 Spring Boot
事件執行相應操做。整個過程大概如此,接下來進行詳細討論,先來看看 SpringApplicationRunListener
定義:
public interface SpringApplicationRunListener { // 在run()方法開始執行時被調用,表示應用剛剛啓動,對應的 Spring Boot 事件爲 ApplicationStartingEvent void starting(); // ConfigurableEnvironment 構建完成時調用,對應的 Spring Boot 事件爲 ApplicationEnvironmentPreparedEvent void environmentPrepared(ConfigurableEnvironment environment); // ApplicationContext 構建完成時調用,對應的 Spring Boot 事件爲 ApplicationContextInitializedEvent void contextPrepared(ConfigurableApplicationContext context); // ApplicationContext 完成加載但還未啓動時調用,對應的 Spring Boot 事件爲 ApplicationPreparedEvent void contextLoaded(ConfigurableApplicationContext context); // ApplicationContext 已啓動,但 callRunners 還未執行時調用,對應的 Spring Boot 事件爲 ApplicationStartedEvent void started(ConfigurableApplicationContext context); // ApplicationContext 啓動完畢被調用,對應的 Spring Boot 事件爲 ApplicationReadyEvent void running(ConfigurableApplicationContext context); // 應用出錯時被調用,對應的 Spring Boot 事件爲 ApplicationFailedEvent void failed(ConfigurableApplicationContext context, Throwable exception); }
咱們來看看它的實現類,也就是上面加載的 spring.factories
文件中的 EventPublishingRunListener
類,該類也是 Spring Boot
內建的惟一實現類,具體廣播事件的操做在該類中進行,代碼以下:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; private final SimpleApplicationEventMulticaster initialMulticaster; public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } @Override public void starting() { this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); } ... }
能夠看到,經過構造方法建立 EventPublishingRunListener
實例的過程當中,調用了 getListeners
方法,將 SpringApplication
中全部 ApplicationListener
監聽器關聯到了 initialMulticaster
屬性中。沒錯,這裏的 ApplicationListener
監聽器就是上篇文章中在 SpringApplication
準備階段從 spring.factories
文件加載的 key 爲 ApplicationListener
的實現類集合,該實現類集合所有重寫了 onApplicationEvent
方法。
這裏又引出了另外一個類, 也就是 SimpleApplicationEventMulticaster
,該類是 Spring
的事件廣播器,也就是經過它來廣播各類事件。接着,當外部迭代的執行到 EventPublishingRunListener
的 starting
方法時,會經過 SimpleApplicationEventMulticaster
的 multicastEvent
方法進行事件的廣播,這裏廣播的是 ApplicationStartingEvent
事件,咱們進入 multicastEvent
方法:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { ... @Override public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } @Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); Executor executor = getTaskExecutor(); for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } } }
經過 getApplicationListeners
方法,根據事件類型返回從上面關聯的 ApplicationListener
集合中篩選出匹配的 ApplicationListener
集合,根據 Spring Boot
版本的不一樣,在這個階段獲取到的監聽器也有可能不一樣,如 2.1.6.BUILD-SNAPSHOT
版本返回的是:
而後依次遍歷這些監聽器,同步或異步的調用 invokeListener
方法:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } ... private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass())) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception and just log a debug message. Log logger = LogFactory.getLog(getClass()); if (logger.isTraceEnabled()) { logger.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }
能夠看到,最終調用的是 doInvokeListener
方法,在該方法中執行了 ApplicationListener
的 onApplicationEvent
方法,入參爲廣播的事件對象。咱們就拿其中一個的監聽器來看看 onApplicationEvent
中的實現,如 BackgroundPreinitializer
類:
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> { ... @Override public void onApplicationEvent(SpringApplicationEvent event) { if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME) && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) { performPreinitialization(); } if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) && preinitializationStarted.get()) { try { preinitializationComplete.await(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } ... }
在該方法中,經過 instanceof
判斷事件的類型,從而進行相應的操做。該監聽器主要的操做是新建一個後臺線程去執行那些耗時的初始化工做,包括驗證器、消息轉換器等。LoggingApplicationListener
監聽器則是對 Spring Boot
的日誌系統作一些初始化的前置操做。另外兩個監聽器在該階段無任何操做。
至此,SpringBoot
事件機制的總體流程大概如此,咱們簡要回顧一下幾個核心組件:
SpringApplicationRunListeners:首先,在 run
方法的執行過程當中,經過該類在 SpringBoot
不一樣的階段調用不一樣的階段方法,如在剛啓動階段調用的 starting
方法。
SpringApplicationRunListener:而 SpringApplicationRunListeners
屬於組合模式的實現,它裏面關聯了 SpringApplicationRunListener
實現類集合,當外部調用階段方法時,會迭代執行該集合中的階段方法。實現類集合是 spring.factories
文件中定義好的類。這裏是一個擴展點,詳細的後面述說。
EventPublishingRunListener:該類是 Spring Boot
內置的 SpringApplicationRunListener
惟一實現類,因此,當外部調用各階段的方法時,真正執行的是該類中的方法。
SimpleApplicationEventMulticaster:在階段方法中,會經過 Spring
的 SimpleApplicationEventMulticaster
事件廣播器,廣播各個階段對應的事件,如這裏的 starting
方法廣播的事件是 ApplicationStartingEvent
。
ApplicationListener:最後 ApplicationListener
的實現類也就是 Spring Boot
監聽器會監聽到廣播的事件,根據不一樣的事件,進行相應的操做。這裏的 Spring Boot
監聽器是也是在 spring.factories
中定義好的,這裏咱們也可自行擴展。
到這裏 Spring Boot
事件監聽機制差很少就結束了,值得注意的是 Spring Boot
監聽器實現的是 Spring
的 ApplicationListener
類,事件類最終繼承的也是 Spring
的 ApplicationEvent
類,因此,Spring Boot
的事件和監聽機制都基於 Spring
而實現的。
當執行完 listeners.starting
方法後,接着進入構造 ApplicationArguments
階段:
public class SpringApplication { ... public ConfigurableApplicationContext run(String... args) { ... SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ... } } ... }
該類是用於簡化 Spring Boot
應用啓動參數的封裝接口,咱們啓動項目時輸入的命令參數會封裝在該類中。一種是經過 IDEA 輸入的參數,以下:
另外一種是 springboot
jar包運行時傳遞的參數:cmd中運行java -jar xxx.jar name=張三 pwa=123
。
而後,能夠經過 @Autowired
注入 ApplicationArguments
的方式進行使用:
public class Test { @Autowired private ApplicationArguments applicationArguments; public void getArgs() { // 獲取 args 中的全部 non option 參數 applicationArguments.getNonOptionArgs(); // 獲取 args 中全部的 option 參數的 name applicationArguments.getOptionNames(); // 獲取傳遞給應用程序的原始未處理參數 applicationArguments.getSourceArgs(); // 獲取 args 中指定 name 的 option 參數的值 applicationArguments.getOptionValues("nmae"); // 判斷從參數中解析的 option 參數是否包含指定名稱的選項 applicationArguments.containsOption("name"); } }
接着進入構造 ConfigurableEnvironment
的階段,該類是用來處理咱們外部化配置的,如 properties
、YAML
等,提供對配置文件的基礎操做。固然,它能處理的外部配置可不只僅如此,詳細的在下篇文章討論,這裏咱們進行簡要了解便可,進入建立該類的 prepareEnvironment
方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
這裏經過 getOrCreateEnvironment
方法返回具體的 Environment
:
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
能夠看到,這裏經過 webApplicationType
屬性來判斷當前應用的類型,有 Servlet
、 Reactive
、 非Web 3種類型,該屬性也是在上篇文章中 SpringApplication
準備階段肯定的,這裏咱們一般都是 Servlet
類型,返回的是 StandardServletEnvironment
實例。
以後,還調用了 SpringApplicationRunListeners
的 environmentPrepared
階段方法,表示 ConfigurableEnvironment
構建完成,同時向 Spring Boot
監聽器發佈 ApplicationEnvironmentPreparedEvent
事件。監聽該事件的監聽器有:
這裏經過 createApplicationContext
方法建立 Spring
應用上下文,實際上 Spring
的應用上下文才是驅動 Spring Boot
的核心引擎:
public class SpringApplication { ... public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot." + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework." + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"; ... protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS); break; case REACTIVE: contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS); break; default: contextClass = Class.forName(DEFAULT_CONTEXT_CLASS); } } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass); } ... }
這裏也是經過 webApplicationType
屬性來肯定應用類型從而建立 String
上下文,上篇文章說到該屬性值是在 Spring Boot
準備階段推導出來的。這裏咱們的應用類型是 Servlet
,因此建立的是 AnnotationConfigServletWebServerApplicationContext
對象。建立完 Spring
應用上下文以後,執行 prepareContext
方法進入準備上下文階段:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, 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 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
咱們來看看主要作了哪些操做:
設置了 Spring
應用上下文的 ApplicationArguments
,上面說過是處理外部化配置的,具體類型爲 StandardServletEnvironment
。
Spring
應用上下文後置處理,主要是覆蓋當前 Spring
應用上下文默認所關聯的 ResourceLoader
和 ClassLoader
。
執行 Spring
的初始化器,上篇文章說過在 Spring Boot
準備階段初始化了一批在 spring.factories
文件中定義好的 ApplicationContextInitializer
,這裏就是執行它們的 initialize
方法,同時這裏也是一個擴展點,後面詳細討論。
執行 SpringApplicationRunListeners
的 contextPrepared
階段方法,表示 ApplicationContext
準備完成,同時向 Spring Boot
監聽器發佈 ApplicationContextInitializedEvent
事件 。
將 springApplicationArguments
和 springBootBanner
註冊爲 Bean
。
加載 Spring
應用上下文的配置源,也是在上篇文章 Spring Boot
準備階段獲取的 primarySources
和 sources
,primarySources
來源於 SpringApplication
構造器參數,sources
則來源於自定義配置的 setSources
方法。
最後執行 SpringApplicationRunListeners
的 contextLoaded
階段方法,表示 ApplicationContext
完成加載但還未啓動,同時向 Spring Boot
監聽器發佈 ApplicationPreparedEvent
事件 。
接下來就是真正啓動階段,執行的是 refreshContext
方法:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
能夠看到,底層調用的是 AbstractApplicationContext
的 refresh
方法,到這裏 Spring
應用正式啓動,Spring Boot
核心特性也隨之啓動,如自動裝配。隨後執行 SpringApplicationRunListeners
的 started
階段方法,表示 ApplicationContext
已啓動,同時向 Spring Boot
監聽器發佈 ApplicationStartedEvent
事件 。但還未啓動完成,後面還有一個 callRunners
方法,通常來說,裏面執行一些咱們自定義的操做。以後 Spring
應用纔算啓動完成,隨後調用 running
方法,發佈 ApplicationReadyEvent
事件。至此,SpringApplication
運行階段結束。
最後來對 SpringApplication
運行階段作一個總結。這個階段核心仍是以啓動 Spring
應用上下文爲主,同時根據應用類型來初始化不一樣的上下文對象,但這些對象的基類都是 Spring
的 ConfigurableApplicationContext
類。且在啓動的各個階段中,使用 SpringApplicationRunListeners
進行事件廣播,回調 Spring Boot
的監聽器。同時還初始化了 ApplicationArguments
、ConfigurableEnvironment
等幾個組件。下篇文章咱們就來討論 Spring Boot
的外部化配置部分,來看看爲何外部的各個組件,如 Redis
、Dubbo
等在 properties
文件中進行相應配置後,就能夠正常使用。
以上就是本章的內容,如過文章中有錯誤或者須要補充的請及時提出,本人感激涕零。
參考:
《Spring Boot 編程思想》 https://www.cnblogs.com/youzhibing/p/9603119.html https://www.jianshu.com/p/b86a7c8b3442 https://www.cnblogs.com/duanxz/p/11243271.html https://www.jianshu.com/p/7a674c59d76e