前言
Spring Boot 版本 2.1.7.RELEASE
本文大致跟蹤了一遍Spring Boot的啟動流程。
請邊debug源代碼邊看本文,因為很多變量細節在debug時才能清晰的觀察到,而本文是無法全部涵蓋所有細節的。
文中如有錯誤遺漏,請留言指正。
相關文章
正文
程序入口
public static void main(String[] args) {
SpringApplication.run(XXXApplication.class, args);
}
SpringApplication.run()
函數內部
return new SpringApplication(primarySources).run(args); //primarySources is XXXApplication.class
1. SpringApplication
的構造函數內部
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
1.1 WebApplicationType.deduceFromClasspath()
根據org.springframework.web.reactive.DispatcherHandler
javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext
等類是否存在來推斷服務器類型。類型包括WebApplicationType.REACTIVE
WebApplicationType.SERVLET
WebApplicationType.NONE
1.2 setInitializers()
和setListeners()
函數為SpringApplication
的成員變量initializers
和listeners
賦值。二者均使用getSpringFactoriesInstances(XXX.class)
方法獲取參數。
1.2.1 getSpringFactoriesInstances(XXX.class)
方法中,參數XXX.class是一個interface。此方法首先會根據接口類的類名,獲取其對應的多個實現類,然后實例化這些實現類,最后返回這些實例。這些實現類就是SpringFactories,是各個jar包中的factory。
getSpringFactoriesInstances()
方法內部通過SpringFactoriesLoader.loadFactoryNames()
方法加載。方法內部會在classpath下尋找路徑為META-INF/spring.factories
的所有文件,此文件的內容格式為properties,文件中的每一行記錄一條interface及其對應的多個實現類。每行的內容形如
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
根據所有的spring.factories
文件,可以構建出一個mapMultiValueMap<String, String>
,其中key為interface,value為其對應的多個實現類組成的list。這個map會存到Map<ClassLoader, MultiValueMap<String, String>> cache
中,方便未來多次查找。
spring.factories
的加載類似于工具類,沒有確切的加載時機。隨時用到隨時加載,只是首次加載時,掃描到的spring.factories
全部放入緩存,后續讀取緩存。
1.2 續
通過getSpringFactoriesInstances()
方法的搜索, SpringApplication
實例的成員變量得到了賦值。initializers
類型為List,包含了ApplicationContextInitializer.class
的多個實現類;listeners
類型為List,包含了ApplicationListener.class
的多個實現類。
2 SpringApplication
結束實例化后,會調用成員方法run()
,run()
中有一些關鍵步驟
2.1 SpringApplicationRunListeners listeners = getRunListeners(args);
2.1.1 此方法首先使用 1.2.1 中提到的getSpringFactoriesInstances(XXX.class)
方法搜索SpringApplicationRunListener.class
的實現類。
SpringApplicationRunListener
interface 定義了SpringApplication.run()
運行過程中會拋出的事件,(這個接口類的命名也可以看出SpringApplication - Run - Listener)。
public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void started(ConfigurableApplicationContext context);
void running(ConfigurableApplicationContext context);
void failed(ConfigurableApplicationContext context, Throwable exception);
}
這些事件都是SpringApplicationEvent
類的子類,后文將會提到這些事件。
SpringApplicationRunListener
接口的實現類SpringApplicationRunListener
將會被搜索出來并實例化。
SpringApplicationRunListener
類內部實例化了一個ApplicationEventMulticaster
成員變量,即SimpleApplicationEventMulticaster
類。將來這個multicaster會用來拋出各個事件。將這個multicaster實例化完成后,又將SpringApplication.listeners
填入到這個multicaster內部,作為其初始值,代碼如下。
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
也就是說META-INF/spring.factories
文件中定義的所有ApplicationListener.class
的實現類都會監聽SpringApplicationEvent
事件。
2.1.2 最終getRunListeners(args)
方法會將多個SpringApplicationRunListener
接口的實現類封裝成SpringApplicationRunListeners
,然后返回。
2.2 listeners.starting();
SpringApplicationRunListener
調用starting()
方法,拋出ApplicationStartingEvent
事件。
2.2.1 SimpleApplicationEventMulticaster
類負責拋出事件。
2.2.1.1 SimpleApplicationEventMulticaster
會首先調用getApplicationListeners()
方法獲取監聽事件的所有listener。由于SimpleApplicationEventMulticaster
類的初始值listener包括了SpringApplication.listeners
(見2.1.1的末尾),所以這些listener會收到所有SpringApplicationEvent
事件。
getApplicationListeners()
中會使用AbstractApplicationEventMulticaster.supportsEvent()
方法來篩選監聽某事件的listener。
篩選出的listener會放入multicaster的retrieverCache
中,以備將來再次查詢。
2.2.1.2 SimpleApplicationEventMulticaster
拿到所有的listener后,對每一個listener調用invokeListener(listener, event);
。各個listener會通過ApplicationListener.onApplicationEvent(E event)
方法實現自己的事件響應邏輯。
2.3 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
此方法執行了以下操作。
2.3.1 ConfigurableEnvironment environment = getOrCreateEnvironment();
此方法根據 1.1 中推斷出的服務器類型,實例化ConfigurableEnvironment
類。
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
2.3.2 configureEnvironment(environment, applicationArguments.getSourceArgs());
本文以StandardServletEnvironment
為例。configureEnvironment()
包含以下步驟。
首先,
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
為environment設置了ApplicationConversionService
實例,用于一些數據格式的轉換。
2.3.2.1 configurePropertySources(environment, args);
為environment配置Property源。
Property源按順序為servletConfigInitParams
servletContextInitParams
systemProperties
systemEnvironment
(這些值是String,來源于多個類的靜態變量)。其實Property源在environment的構造函數中就初始化了一部分。具體見各Environment類的構造函數代碼。
systemProperties
內的數據來源于System.getProperties();
systemEnvironment
內的數據來源于System.getenv();
2.3.2.2 configureProfiles(environment, args);
從上一步 2.3.2.1 配置的各個Property源中尋找當前激活的Profile
property key 為"spring.profiles.active"
2.3.2.1中的數據源中沒有"spring.profiles.active"的話,本步驟結束后,activeProfiles
依舊為空。
2.3.3 listeners.environmentPrepared(environment);
發布ApplicationEnvironmentPreparedEvent
事件
2.3.3.1 BootstrapApplicationListener
會響應ApplicationEnvironmentPreparedEvent
事件。
此listener會new StandardEnvironment()
,與主流程的StandardServletEnvironment
相互獨立。在這個獨立的環境下,通過硬編碼的方式加載bootstrap配置文件(如:bootstrap.yml)。
相關代碼通過這個字段可查到。org.springframework.cloud.bootstrap.BootstrapApplicationListener#BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap"
在org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext
方法中,通過硬編碼的方式增加spring.config.name = bootstrap
配置。
然后在加載配置文件過程中的org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchNames
方法通過條件判斷,選擇加載bootstrap配置。下一節2.3.3.2中會在這個方法的判斷中,選擇加載application配置。
2.3.3.2 ConfigFileApplicationListener
會響應ApplicationEnvironmentPreparedEvent
事件。
此listener會在一些路徑下尋找active profile,并加載profile內配置的properties,執行過程中的一些細節如下。
- 搜索路徑包括"classpath:/,classpath:/config/,file:./,file:./config/"。
- 通過
org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments()
方法加載配置文件。關鍵代碼是
List<PropertySource<?>> loaded = loader.load(name, resource);
配置文件路徑名舉例"classpath:/application.yml" "classpath:/application-dev.yml" - 加載好的配置文件會被封裝成
PropertySources
,并存儲在org.springframework.boot.context.config.ConfigFileApplicationListener.Loader.loaded
成員變量中。
2.4 Banner printedBanner = printBanner(environment);
打印Banner,就是在控制臺里打印那個SpringBoot圖形。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.7.RELEASE)
2.5 context = createApplicationContext();
根據webApplicationType
實例化ConfigurableApplicationContext
。
如,SERVLET
類型對應的class是org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
2.6 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
以下是方法內的一些關鍵步驟。
2.6.1 applyInitializers(context);
使用SpringApplication.initializers
內的各個initializer(見1.2),對context
進行初始化。
2.6.2 listeners.contextPrepared(context);
發布ApplicationContextInitializedEvent
事件
2.6.3 load(context, sources.toArray(new Object[0]));
具體執行的操作如下。
2.6.3.1 BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
BeanDefinitionLoader
的作用是可以從XML和java注解中加載bean definition。
這行代碼里的嵌套調用方法getBeanDefinitionRegistry(context)
值得說一下。
2.6.3.1.1 getBeanDefinitionRegistry(context)
這里的context一般都是GenericApplicationContext
的子類。
GenericApplicationContext
類中有一個成員變量
private final DefaultListableBeanFactory beanFactory;
GenericApplicationContext
類和DefaultListableBeanFactory
類都實現了BeanDefinitionRegistry
接口。GenericApplicationContext
類其實是DefaultListableBeanFactory beanFactory
的代理,它將所有對
BeanDefinitionRegistry
接口的調用都交給了內部成員變量DefaultListableBeanFactory beanFactory
去處理。
所以getBeanDefinitionRegistry(context)
方法的調用一般得到的還是context自身。
2.6.3.2 loader.load();
將程序入口處的XXXpplication.class
封裝成BeanDefinition
,并將其注冊到context
中(因為context實現了BeanDefinitionRegistry
接口)。
2.6.4 listeners.contextLoaded(context);
將SpringApplication.listeners
list中的listener全部加入到context中。
發布ApplicationPreparedEvent事件。
listener會在這個時候向context中加入一些BeanFactoryPostProcessor
。
2.7 refreshContext(context);
這里將會執行很多任務。
首先是調用AbstractApplicationContext.refresh()
方法,此方法中一些關鍵步驟如下。
2.7.1 prepareRefresh();
設置AbstractApplicationContext
為active
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
2.7.2 prepareBeanFactory(beanFactory);
為beanFactory設置一些值,具體見源碼。
2.7.3 invokeBeanFactoryPostProcessors(beanFactory);
其中包括ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
。
正如其注釋所言,ConfigurationClassPostProcessor
會在此時掃描并注冊BeanDefinition。
Derive further bean definitions from the configuration classes in the registry.
其中的核心方法為ConfigurationClassParser.parse()
。
2.7.3.1
由于入口類使用@SpringBootApplication
注解,該注解包含了@ComponentScan
。
ConfigurationClassParser.parse()
方法中首先會調用一次
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass()
方法。由于入口類作為一個Bean,且被@ComponentScan
注解,所以會自動掃描一下所有的java類來尋找Bean,但此次掃描的basePackages會使用main函數所在類的package。
例如com.demo.SomeTestApplication
,則只會掃描com.demo
下的bean。
2.7.3.2
在ConfigurationClassParser.parse()
方法的末尾處this.deferredImportSelectorHandler.process()
會進行@Import
注解的處理。
由于@SpringBootApplication
注解包含了@EnableAutoConfiguration
。而@EnableAutoConfiguration
又包含了@Import(AutoConfigurationImportSelector.class)
。
所以會使用AutoConfigurationImportSelector.getCandidateConfigurations()
方法對被@EnableAutoConfiguration
注解的類進行加載。而獲取這些被@EnableAutoConfiguration
注解的類的方式是使用1.2.1中提到的SpringFactoriesLoader.loadFactoryNames()
方法。即在META-INF/spring.factories
文件中,將這些類配置為org.springframework.boot.autoconfigure.EnableAutoConfiguration
的對應值,才能被識別。
這些被識別出的class會作為ConfigurationClass
放入ConfigurationClassParser.configurationClasses
這一map中。
被引入的ConfigurationClass
也可以通過被@Import(XXXXImportSelector.class)
注解的方式繼續import其他ConfigurationClass
。
2.7.3 續
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
方法的后續,會通過
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
取出 2.7.3.2 中收集到的ConfigurationClass
,使用
ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()
方法加載所有ConfigurationClass
中配置的Bean。
2.7.4 onRefresh();, 此函數只做一件重要的事,就是
createWebServer();首先在beanFactory尋找并實例化
ServletWebServerFactory對應的bean,默認找到bean是
TomcatServletWebServerFactory`。
注:
spring-boot-dependencies.pom文件中默認定義了依賴tomcat-embed-core.jar包,沒有依賴其他的web容器jar包。在 2.7.3 中掃描BeanDefinition時,發現了ServletWebServerFactoryConfiguration
配置類。在該配置類中定義了多個WebServer的bean方法,包括Tomcat,Jetty和Undertow。但是因為只有Tomcat.class
等tomcat相關類存在,所以只有tomcatServletWebServerFactory()
bean method生效,默認實例化的便是TomcatServletWebServerFactory
。
Tomcat相關流程的源碼分析在本文開頭的相關文章中介紹,本文不再贅述。
this.webServer = factory.getWebServer(getSelfInitializer());
生成tomcat實例并啟動tomcat。
2.7.5 初始化剩余的(non-lazy-init)單例的bean,一般controller啥的就會在這里被實例化。
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
2.7.6 finishRefresh();
2.7.6.1 publishEvent(new ContextRefreshedEvent(this));
發布ContextRefreshedEvent
事件
2.7.6.2 WebServer webServer = startWebServer();
啟動TomcatWebServer
,與2.7.4中的啟動tomcat時不同的。
2.7.6.3 publishEvent(new ServletWebServerInitializedEvent(webServer, this));
發布ServletWebServerInitializedEvent
事件
2.7 續 最后回到SpringApplication.refreshContext(context);
方法內部
registerShutdownHook
2.8 listeners.started(context);
發布ApplicationStartedEvent
事件
2.9 listeners.running(context);
發布ApplicationReadyEvent
事件
3 至此,SpringApplication.run()
函數全部執行完畢,也就意味著main()函數執行完畢。main線程結束,java虛擬機啟動一個DestroyJavaVM線程,并等待其他的非daemon線程執行結束。
附:SpringApplicationEvent
各個事件的拋出位置
事件類型(SpringApplicationEvent的子類) | SpringApplicationRunListener接口的調用方法 | 本文的章節號 | |
---|---|---|---|
ApplicationStartingEvent | starting() | 2.2 | |
ApplicationEnvironmentPreparedEvent | environmentPrepared() | 2.3.3 | |
ApplicationContextInitializedEvent | contextPrepared() | 2.6.2 | |
ApplicationPreparedEvent | contextLoaded() | 2.6.4 | |
ApplicationStartedEvent | started() | 2.8 | |
ApplicationReadyEvent | running() | 2.9 | · |