spring5/springboot2源碼學習 -- spring boot 應用的啟動過程

  • <strong>
  • <strong>
  • <strong>

基本環境

開發工具:Intellij IDEA 2017(盜)版

java版本:1.8.0_151

spring的github地址:spring-framework

準備:git clone或直接下載github上的spring源碼,導入idea中,在項目路徑下執行gradle build (如果本機沒有gradle環境,或者版本差很多,就用gradlew代替),會build很久,可以事先將阿里的maven倉庫地址加到repositories中,像這樣:

<code>repositories {  maven {            url "http://maven.aliyun.com/nexus/content/groups/public/"        }        maven { url "https://repo.spring.io/plugins-release" }}/<code>

會用到的縮寫:

<code>ApplicationContext -> ACApplicationContextInitializer -> ACIBeanFactory -> BFApplicationListener -> ALEnvironmentPostProcessor -> EPPspring boot 應用的啟動/*** @author pk* @date 2018/02/22*/@SpringBootApplicationpublic class SpringBootTwoStudyApplication {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(SpringBootTwoStudyApplication.class);//可以在run之前,對AC進行一些自定義的配置,添加點ApplicationListener,ApplicationContextInitializer啥的springApplication.run(args);}}/<code>


spring5/springboot2源碼學習 -- spring boot 應用的啟動過程


一.SpringApplication的初始化

代碼如下:

<code>public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {  this.resourceLoader = resourceLoader;        Assert.notNull(primarySources, "PrimarySources must not be null");        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//1        this.webApplicationType = deduceWebApplicationType();//2        setInitializers((Collection) getSpringFactoriesInstances(                ApplicationContextInitializer.class));//3        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//4        this.mainApplicationClass = deduceMainApplicationClass();//5    }/<code>

1.設置啟動類primarySources到對應屬性

這個啟動類就是在初始化SpringApplication時候的參數,可以有多個

2.獲取web應用類型

  • 根據當前classpath下存在的類來判斷的:
  • 如果存在org.springframework.web.reactive.DispatcherHandler,就是REACTIVE
  • 如果不存在org.springframework.web.servlet.DispatcherServlet或者javax.servlet.Servlet,就是NONE
  • 否則,就是SERVLET

3.設置ApplicationContextInitializer

代碼如下:

<code>private  Collection getSpringFactoriesInstances(Class 
type, Class>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader();//3.1 Set<string> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));//3.2 List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);//3.3 AnnotationAwareOrderComparator.sort(instances);//3.4 return instances; }/<string>
/<code>

3.1 獲取線程上下文ClassLoader

TODO(ClassLoader的相關解釋)

3.2 使用SpringFactoriesLoader加載出classpath下所有路徑為META-INF/spring.factories的資源文件,並讀取key為ApplicationContextInitializer的值

會得到如些幾個類(實際是全稱類名):

<code>SharedMetadataReaderFactoryContextInitializerAutoConfigurationReportLoggingInitializerConfigurationWarningsACIContextIdACIDelegatingACIServerPortInfoACI/<code>

3.3 利用反射創建出上述ACI的實例

3.4 排序

根據Ordered接口/@PriorityOrder註解/@Order註解去排

其他地方的排序基本也是按照這個規則來排的

4.設置listener

跟設置ACI的方法一樣,也是讀取spring.factories文件中的對應key項所對應的所有類名,然後實例化、排序.

得到如下幾個ApplicationListener:

<code>BackgroundPreinitializerClearCachesALParentContextCloserALFileEncodingALAnsiOutputALConfigFileALDelegatingALClasspathLoggingALLoggingALLiquibaseServiceLocatorAL/<code>

5.設置mainApplicationClass

就是找到main方法所在的類.是唯一的,跟上面的primarySources不是一回事

二.SpringApplication的run(String … args)方法

代碼如下:

<code>public ConfigurableApplicationContext run(String... args) {  StopWatch stopWatch = new StopWatch();        stopWatch.start();//1        ConfigurableApplicationContext context = null;        Collection<springbootexceptionreporter> exceptionReporters = new ArrayList<>();        configureHeadlessProperty();//2        SpringApplicationRunListeners listeners = getRunListeners(args);//3        listeners.starting();//4        try {            ApplicationArguments applicationArguments = new DefaultApplicationArguments(                    args);//5            ConfigurableEnvironment environment = prepareEnvironment(listeners,                    applicationArguments);//6            configureIgnoreBeanInfo(environment);//7            Banner printedBanner = printBanner(environment);//8            context = createApplicationContext();//9            exceptionReporters = getSpringFactoriesInstances(                    SpringBootExceptionReporter.class,                    new Class[] { ConfigurableApplicationContext.class }, context);//10            prepareContext(context, environment, listeners, applicationArguments,                    printedBanner);//11            refreshContext(context);//12            afterRefresh(context, applicationArguments);//13            listeners.finished(context, null);//14            stopWatch.stop();            //15            if (this.logStartupInfo) {                new StartupInfoLogger(this.mainApplicationClass)                        .logStarted(getApplicationLog(), stopWatch);            }            callRunners(context, applicationArguments);//16            return context;        }        catch (Throwable ex) {            handleRunFailure(context, listeners, exceptionReporters, ex);            throw new IllegalStateException(ex);        }    }/<springbootexceptionreporter>/<code>

1. 創建一個StopWatch用於記錄啟動的時間

2. 配置java.awt.headless屬性,默認為true

這個headless是一種模式,是指缺少顯示屏、鍵盤或者鼠標時的系統配置

我找了spring和spring boot的源碼,只有ImageBanner裡面用到了

3.從spring.factories中加載出SpringApplicationRunListener,並構建一個SpringApplicationRunListeners

  • StringApplicationListeners就是對對個SpringApplicationRunListener的包裝
  • 現在看只有一個具體實現類
  • EventPublishingRunListener
  • 雖然這個類叫做listener,但其實起的作用是傳播事件,更像是一個ApplicationEventMulticaster。
  • spring boot啟動過程中的事件,主要由這個類來傳播

4.發佈一個ApplicationStartingEvent事件

  • 關於spring事件發佈機制,請看spring事件機制
  • 響應的AL
  • LoggingAL:初始化日誌系統
  • 做法是按照logback>log4j>jul的順序查找classpath下存在的相關類
  • LiquibaseServiceLocatorAL:實在是沒有用過這個東西,不知道這個LiquibaseService是個啥…

5.根據啟動時的命令行參數構建一個ApplicationArguments

ApplicationArguments的接口定義:

<code>public interface ApplicationArguments {  String[] getSourceArgs();    //--開頭的參數    Set<string> getOptionNames();    boolean containsOption(String name);    //--開頭的參數是允許重複定義的,不會覆蓋      List<string> getOptionValues(String name);    List<string> getNonOptionArgs();}/<string>/<string>/<string>/<code>

這個類的作用就是解析所有的啟動參數的

6.prepareEnvironment():創建並配置environment

代碼如下:

<code>private ConfigurableEnvironment prepareEnvironment(  SpringApplicationRunListeners listeners,            ApplicationArguments applicationArguments) {        ConfigurableEnvironment environment = getOrCreateEnvironment();//6.1        configureEnvironment(environment, applicationArguments.getSourceArgs());//6.2        listeners.environmentPrepared(environment);//6.3        bindToSpringApplication(environment);//6.4        if (this.webApplicationType == WebApplicationType.NONE) {            environment = new EnvironmentConverter(getClassLoader())                    .convertToStandardEnvironmentIfNecessary(environment);        }        ConfigurationPropertySources.attach(environment);//6.5        return environment;    }/<code>

6.1 根據WebApplicationType創建對應類型的environment

關於spring中Environment/PropertySource的介紹:

根據webApplicationType:

<code>SERVLET -> StandardServletEnvironmentNONE/REACTIVE -> StandardEnvironment/<code>

初始化的時候會在構造函數中調用customizePropertySources()方法,其結果是會在environment的propertySources屬性的propertySourcesList列表中加入以下PropertySource:

  • 代表ServletConfig的構建參數的,名為servletConfigInitParams的StubPropertySource(目前只是作為佔位符存在)
  • 代表ServletContext的構建參數的,名為servletContextInitParams的StubPropertySource(目前只是作為佔位符存在)
  • 代表jvm屬性參數的名為systemProperties的MapPropertySource(來源於System.getProperties()方法)
  • 代表環境變量的名為systemEnvironment的SystemEnvironmentPropertySource(來源於System.getEnv()方法)

6.2 配置environment

配置PropertySources

  • 看看SpringApplication的defaultProperties屬性是否為空,如果不為空則加入一個MapPropertySource到propertySources中。這個defaultProperties屬性可以在SpringApplication調用run方法之前通過setDefaultProperties()方法設置
  • 看看有沒有命令號參數,如果有則創建一個SimpleCommandLinePropertySource加入到propertySources中

配置activeProfiles:就是將spring.profiles.active屬性的值作為數組讀入

6.3 listeners發佈一個ApplicationEnvironmentPreparedEvent

這是一個很重要的事件,響應的listener有很多:

ConfigFileApplicationListener

  • 從spring.factories中讀取所有的EnvironmentPostProcessor,它自己也是個EPP。
  • 排序,然後逐個調用
  • SystemEnvironmentPropertySourceEPP:替換systemEnvironment為其內部類OriginAwareSystemEnvironmentPropertySource
  • SpringApplicationJsonEPP:解析spring.application.json屬性,作為JsonPropertySource加入propertySources中

CloudFoundryVcapEPP:VCAP相關,不懂,略過

ConfigFileAL(自己):

  • 添加一個RandomValuePropertySource,作用是設置所有random.int/long開頭的屬性為一個隨機值
  • 從spring.factories中讀取PropertySourceLoader:
  • PropertiesPropertySourceLoader:讀取.properties配置文件
  • YamlPropertySourceLoader:讀取.yaml配置文件
  • 根據activeProfiles解析每個對應的配置文件成OriginTrackedMapPropertySource,加入到propertySources中

6.4 將environment綁定到SpringApplication中

todo…

6.5 往propertySources中加入一個ConfigurationPropertySourcesPropertySource

7.配置一個spring.beaninfo.ignore屬性,默認為true

8.打印banner

9.根據webApplicationType構建ApplicationContext

<code>SERVLET -> AnnotationConfigServletWebServerACREACTIVE -> ReactiveWebServerACNONE -> AnnotationConfigAC /<code>

按照繼承結構:

DefaultResourceLoader:ResourceLoader的默認實現,作用就是加載Resource,內部有個比較重要的ProtocolResolver集合屬性,具體使用請看ProtocolResolver解析

AbstractAC:配置了一個ResourcePatternResolver,具體請看Resource相關

GenericAC:初始化了一個BeanFactory,具體類型是DefaultListableBeanFactory


原文鏈接:https://blog.csdn.net/yuxiuzhiai/article/details/79074249


分享到:


相關文章: