https://blog.csdn.net/dm_vincent/article/details/76735888
關於Spring Boot,已經有很多介紹其如何使用的文章了,本文從源代碼(基於Spring-boot 1.5.6)的角度來看看Spring Boot的啟動過程到底是怎么樣的,為何以往紛繁復雜的配置到如今可以這么簡便。
1. 入口類
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
以上的代碼就是通過Spring Initializr配置生成的一個最簡單的Web項目(只引入了Web功能)的入口方法。這個想必只要是接觸過Spring Boot都會很熟悉。簡單的方法背后掩藏的是Spring Boot在啟動過程中的復雜性,本文的目的就是一探這里面的究竟。
1.1 注解@SpringBootApplication
而在看這個方法的實現之前,需要看看@SpringBootApplication這個注解的功能:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )} ) public @interface SpringBootApplication { // ... }
很明顯的,這個注解就是三個常用在一起的注解@SpringBootConfiguration,@EnableAutoConfiguration以及@ComponentScan的組合,並沒有什么高深的地方。
1.1.1 @SpringBootConfiguration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { }
這個注解實際上和@Configuration有相同的作用,配備了該注解的類就能夠以JavaConfig的方式完成一些配置,可以不再使用XML配置。
1.1.2 @ComponentScan
顧名思義,這個注解完成的是自動掃描的功能,相當於Spring XML配置文件中的:
<context:component-scan>
可以使用basePackages屬性指定要掃描的包,以及掃描的條件。如果不設置的話默認掃描@ComponentScan注解所在類的同級類和同級目錄下的所有類,所以對於一個Spring Boot項目,一般會把入口類放在頂層目錄中,這樣就能夠保證源碼目錄下的所有類都能夠被掃描到。
1.1.3 @EnableAutoConfiguration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
這個注解是讓Spring Boot的配置能夠如此簡化的關鍵性注解。目前知道這個注解的作用就可以了,關於自動配置不再本文討論范圍內,后面如果有機會另起文章專門分析這個自動配置的實現原理。
2. 入口方法
2.1 SpringApplication的實例化
介紹完了入口類,下面開始分析關鍵方法:
SpringApplication.run(DemoApplication.class, args);
相應實現:
// 參數對應的就是DemoApplication.class以及main方法中的args public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } // 最終運行的這個重載方法 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
它實際上會構造一個SpringApplication的實例,然后運行它的run方法:
// 構造實例
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 = deduceWebApplicationType(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
在構造函數中,主要做了4件事情:
2.1.1 推斷應用類型是Standard還是Web
private WebApplicationType deduceWebApplicationType() { if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; } // 相關常量 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
可能會出現三種結果:
- WebApplicationType.REACTIVE - 當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS並且不存在MVC_WEB_ENVIRONMENT_CLASS時
- WebApplicationType.NONE - 也就是非Web型應用(Standard型),此時類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時
- WebApplicationType.SERVLET - 類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的所有類型時
2.1.2 設置初始化器(Initializer)
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
這里出現了一個新的概念 - 初始化器。
先來看看代碼,再來嘗試解釋一下它是干嘛的:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } // 這里的入參type就是ApplicationContextInitializer.class private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // 使用Set保存names來避免重復元素 Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根據names來進行實例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 對實例進行排序 AnnotationAwareOrderComparator.sort(instances); return instances; }
這里面首先會根據入參type讀取所有的names(是一個String集合),然后根據這個集合來完成對應的實例化操作:
// 入參就是ApplicationContextInitializer.class public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories"); ArrayList result = new ArrayList(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException var8) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8); } }
這個方法會嘗試從類路徑的META-INF/spring.factories處讀取相應配置文件,然后進行遍歷,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包為例,它的META-INF/spring.factories部分定義如下所示:
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
因此這兩個類名會被讀取出來,然后放入到集合中,准備開始下面的實例化操作:
// 關鍵參數:
// type: org.springframework.context.ApplicationContextInitializer.class
// names: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<T>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass .getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; }
初始化步驟很直觀,沒什么好說的,類加載,確認被加載的類確實是org.springframework.context.ApplicationContextInitializer的子類,然后就是得到構造器進行初始化,最后放入到實例列表中。
因此,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個接口是這樣定義的:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { /** * Initialize the given application context. * @param applicationContext the application to configure */ void initialize(C applicationContext); }
根據類文檔,這個接口的主要功能是:
在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,注冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體。
2.1.3. 設置監聽器(Listener)
設置完了初始化器,下面開始設置監聽器:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
同樣地,監聽器也是一個新概念,還是從代碼入手:
// 這里的入參type是:org.springframework.context.ApplicationListener.class private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
可以發現,這個加載相應的類名,然后完成實例化的過程和上面在設置初始化器時如出一轍,同樣,還是以spring-boot-autoconfigure這個包中的spring.factories為例,看看相應的Key-Value:
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
至於ApplicationListener接口,它是Spring框架中一個相當基礎的接口了,代碼如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
這個接口基於JDK中的EventListener接口,實現了觀察者模式。對於Spring框架的觀察者模式實現,它限定感興趣的事件類型需要是ApplicationEvent類型的子類,而這個類同樣是繼承自JDK中的EventObject類。
2.1.4. 推斷應用入口類
this.mainApplicationClass = deduceMainApplicationClass();
- 1
這個方法的實現有點意思:
private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
它通過構造一個運行時異常,通過異常棧中方法名為main的棧幀來得到入口類的名字。
至此,對於SpringApplication實例的初始化過程就結束了。
2.2 SpringApplication.run方法
完成了實例化,下面開始調用run方法:
// 運行run方法 public ConfigurableApplicationContext run(String... args) { // 計時工具 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 設置java.awt.headless系統屬性為true - 沒有圖形化界面 configureHeadlessProperty(); // KEY 1 - 獲取SpringApplicationRunListeners SpringApplicationRunListeners listeners = getRunListeners(args); // 發出開始執行的事件 listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // KEY 2 - 根據SpringApplicationRunListeners以及參數來准備環境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); // 准備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體 Banner printedBanner = printBanner(environment); // KEY 3 - 創建Spring上下文 context = createApplicationContext(); // 准備異常報告器 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // KEY 4 - Spring上下文前置處理 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // KEY 5 - Spring上下文刷新 refreshContext(context); // KEY 6 - Spring上下文后置處理 afterRefresh(context, applicationArguments); // 發出結束執行的事件 listeners.finished(context, null); // 停止計時器 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } }
這個run方法包含的內容也是有點多的,根據上面列舉出的關鍵步驟逐個進行分析:
2.2.1 第一步 - 獲取所謂的run listeners:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }
這里仍然利用了getSpringFactoriesInstances方法來獲取實例:
// 這里的入參: // type: SpringApplicationRunListener.class // parameterTypes: new Class<?>[] { SpringApplication.class, String[].class }; // args: SpringApplication實例本身 + main方法傳入的args private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
所以這里還是故技重施,從META-INF/spring.factories中讀取Key為org.springframework.boot.SpringApplicationRunListener的Values:
比如在spring-boot包中的定義的spring.factories:
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
我們來看看這個EventPublishingRunListener是干嘛的:
/** * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s. * <p> * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired * before the context is actually refreshed. * * @author Phillip Webb * @author Stephane Nicoll */ public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { // ... }
從類文檔可以看出,它主要是負責發布SpringApplicationEvent事件的,它會利用一個內部的ApplicationEventMulticaster在上下文實際被刷新之前對事件進行處理。至於具體的應用場景,后面用到的時候再來分析。
2.2.2 第二步 - 根據SpringApplicationRunListeners以及參數來准備環境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); if (!this.webEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } return environment; }
配置環境的方法:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { configurePropertySources(environment, args); configureProfiles(environment, args); }
所以這里實際上也包含了兩個步驟:
- 配置Property Sources
- 配置Profiles
具體實現這里就不展開了,代碼也比較直觀。
對於Web應用而言,得到的environment變量是一個StandardServletEnvironment的實例。得到實例后,會調用前面RunListeners中的environmentPrepared方法:
@Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); }
在這里,定義的廣播器就派上用場了,它會發布一個ApplicationEnvironmentPreparedEvent事件。
那么有發布就有監聽,在構建SpringApplication實例的時候不是初始化過一些ApplicationListeners嘛,其中的Listener就可能會監聽ApplicationEnvironmentPreparedEvent事件,然后進行相應處理。
所以這里SpringApplicationRunListeners的用途和目的也比較明顯了,它實際上是一個事件中轉器,它能夠感知到Spring Boot啟動過程中產生的事件,然后有選擇性的將事件進行中轉。為何是有選擇性的,看看它的實現就知道了:
@Override public void contextPrepared(ConfigurableApplicationContext context) { }
它的contextPrepared方法實現為空,沒有利用內部的initialMulticaster進行事件的派發。因此即便是外部有ApplicationListener對這個事件有興趣,也是沒有辦法監聽到的。
那么既然有事件的轉發,是誰在監聽這些事件呢,在這個類的構造器中交待了:
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); } }
前面在構建SpringApplication實例過程中設置的監聽器在這里被逐個添加到了initialMulticaster對應的ApplicationListener列表中。所以當initialMulticaster調用multicastEvent方法時,這些Listeners中定義的相應方法就會被觸發了。
2.2.3 第三步 - 創建Spring上下文
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); } // WEB應用的上下文類型 public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
這個上下文類型的類圖如下所示:
這也是相當復雜的一個類圖了,如果能把這張圖中的各個類型的作用弄清楚,估計也是一個Spring大神了 :)
對於我們的Web應用,上下文類型就是DEFAULT_WEB_CONTEXT_CLASS。
2.2.4 第四步 - Spring上下文前置處理
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 將環境和上下文關聯起來 context.setEnvironment(environment); // 為上下文配置Bean生成器以及資源加載器(如果它們非空) postProcessApplicationContext(context); // 調用初始化器 applyInitializers(context); // 觸發Spring Boot啟動過程的contextPrepared事件 listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // 添加兩個Spring Boot中的特殊單例Beans - springApplicationArguments以及springBootBanner context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // 加載sources - 對於DemoApplication而言,這里的sources集合只包含了它一個class對象 Set<Object> sources = getSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 加載動作 - 構造BeanDefinitionLoader並完成Bean定義的加載 load(context, sources.toArray(new Object[sources.size()])); // 觸發Spring Boot啟動過程的contextLoaded事件 listeners.contextLoaded(context); }
關鍵步驟:
配置Bean生成器以及資源加載器(如果它們非空):
protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context) .setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context) .setClassLoader(this.resourceLoader.getClassLoader()); } } }
調用初始化器
protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { Class<?> requiredType = GenericTypeResolver.resolveTypeArgument( initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }
這里終於用到了在創建SpringApplication實例時設置的初始化器了,依次對它們進行遍歷,並調用initialize方法。
2.2.5 第五步 - Spring上下文刷新
private void refreshContext(ConfigurableApplicationContext context) { // 由於這里需要調用父類一系列的refresh操作,涉及到了很多核心操作,因此耗時會比較長,本文不做具體展開 refresh(context); // 注冊一個關閉容器時的鈎子函數 if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } } // 調用父類的refresh方法完成容器刷新的基礎操作 protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext)applicationContext).refresh(); }
注冊關閉容器時的鈎子函數的默認實現是在AbstractApplicationContext類中:
public void registerShutdownHook() { if(this.shutdownHook == null) { this.shutdownHook = new Thread() { public void run() { synchronized(AbstractApplicationContext.this.startupShutdownMonitor) { AbstractApplicationContext.this.doClose(); } } }; Runtime.getRuntime().addShutdownHook(this.shutdownHook); } }
如果沒有提供自定義的shutdownHook,那么會生成一個默認的,並添加到Runtime中。默認行為就是調用它的doClose方法,完成一些容器銷毀時的清理工作。
2.2.6 第六步 - Spring上下文后置處理
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { callRunners(context, args); } private void callRunners(ApplicationContext context, ApplicationArguments args) { List<Object> runners = new ArrayList<Object>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<Object>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } } private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); } catch (Exception ex) { throw new IllegalStateException("Failed to execute ApplicationRunner", ex); } } private void callRunner(CommandLineRunner runner, ApplicationArguments args) { try { (runner).run(args.getSourceArgs()); } catch (Exception ex) { throw new IllegalStateException("Failed to execute CommandLineRunner", ex); } }
所謂的后置操作,就是在容器完成刷新后,依次調用注冊的Runners。Runners可以是兩個接口的實現類:
- org.springframework.boot.ApplicationRunner
- org.springframework.boot.CommandLineRunner
這兩個接口有什么區別呢:
/** * Interface used to indicate that a bean should <em>run</em> when it is contained within * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined * within the same application context and can be ordered using the {@link Ordered} * interface or {@link Order @Order} annotation. * * @author Phillip Webb * @since 1.3.0 * @see CommandLineRunner */ public interface ApplicationRunner { /** * Callback used to run the bean. * @param args incoming application arguments * @throws Exception on error */ void run(ApplicationArguments args) throws Exception; } /** * Interface used to indicate that a bean should <em>run</em> when it is contained within * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined * within the same application context and can be ordered using the {@link Ordered} * interface or {@link Order @Order} annotation. * <p> * If you need access to {@link ApplicationArguments} instead of the raw String array * consider using {@link ApplicationRunner}. * * @author Dave Syer * @see ApplicationRunner */ public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; }
其實沒有什么不同之處,除了接口中的run方法接受的參數類型是不一樣的以外。一個是封裝好的ApplicationArguments類型,另一個是直接的String不定長數組類型。因此根據需要選擇相應的接口實現即可。
至此,SpringApplication的run方法就分析完畢了。
3. 總結
本文分析了Spring Boot啟動時的關鍵步驟,主要包含以下兩個方面:
-
SpringApplication實例的構建過程
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都通過META-INF/spring.factories完成定義。
-
SpringApplication實例run方法的執行過程
其中主要有一個SpringApplicationRunListeners的概念,它作為Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication實例的構建過程中得到的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴展性。
如果從可擴展性的角度出發,應用開發者可以在Spring Boot容器的啟動階段,擴展哪些內容呢:
- 初始化器(Initializer)
- 監聽器(Listener)
- 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的實現類)
- 啟動期間在Console打印Banner的具體實現類