SpringBoot啟動流程的源碼分析

前言

Spring Boot 版本 2.1.7.RELEASE
本文大致跟蹤了一遍Spring Boot的啟動流程。
請邊debug源代碼邊看本文,因為很多變量細節在debug時才能清晰的觀察到,而本文是無法全部涵蓋所有細節的。
文中如有錯誤遺漏,請留言指正。

相關文章

SpringBoot中Tomcat的源碼分析

正文

程序入口

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的成員變量initializerslisteners賦值。二者均使用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.listenerslist中的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 ·
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容