Spring Boot啟動流程

濤姐濤哥發表於2022-04-04

Spring Boot啟動流程

 

        君生我未生,君生我已老。君恨我生遲,我恨君生早。

 

 一、簡述

Spring Boot啟動流程分析使用版本SpringBoot VERSION:版本 2.5.5-SNAPSHOT。

Spring Boot專案最簡單的Application啟動類。

可以看出Application啟動類中,包含了@SpringBootApplication 註解和 SpringApplication.run 啟動方法,所以SpringBoot的啟動可以分解為 註解啟動方法 兩大過程,而仔細看啟動類中還引入了一個【org.springframework.boot.SpringApplication】包,所以啟動方法中又可以分為兩個階段即 建立SpringApplication 例項執行run方法

二、註解

註解暫且簡單瞭解,暫不深入。

1、@SpirngBootApplication註解

進入@SpringBootApplication註解內。

從@SpringBootApplication註解內部可以發現,它雖然定義使用了多個Annotation進行了原資訊標註,但實際上重要的只有三個Annotation:

  • @SpringBootConfiguration(@SpringBootConfiguration註解點開檢視發現裡面還是應用了@Configuration)->Spring IOC容器配置類。
  • @EnableAutoConfiguration ->使用@Import將所有符合自動配置條件的bean定義載入到IOC容器。
  • @ComponentScan ->自動掃描並載入符合條件的元件或者bean定義,預設掃描SpringApplication的run方法裡的class所在的包路徑下檔案,所以通常將該啟動類放到根包路徑下。

即 @SpringBootApplication = (預設屬性)@Configuration + @EnableAutoConfiguration + @ComponentScan。

三、啟動方法

啟動方法中分為兩個階段即 建立SpringApplication 例項執行run方法

1、建立SpringApplication例項

從啟動類中的run方法跟進去,SpringApplication.run -> return run -> return new SpringApplication(primarySources).run(args) -> this(null, primarySources) -> SpringApplication

其中:return new SpringApplication(primarySources).run(args) ,如果跟new SpringApplication(primarySources) 方法則是啟動方法中的第一階段即建立SpringApplication例項,跟run(args) 方法進去就是啟動方法中的第二階段。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
Spring Boot啟動流程
 1 /**
 2      * Create a new {@link SpringApplication} instance. The application context will load
 3      * beans from the specified primary sources (see {@link SpringApplication class-level}
 4      * documentation for details. The instance can be customized before calling
 5      * {@link #run(String...)}.
 6      *
 7      * @param resourceLoader the resource loader to use
 8      * @param primarySources the primary bean sources
 9      * @see #run(Class, String[])
10      * @see #setSources(Set)
11      */
12     @SuppressWarnings({"unchecked", "rawtypes"})
13     public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
14         // 初始化類載入器
15         this.resourceLoader = resourceLoader;
16         // Assert 斷言非空,若傳入的class引數為null則列印異常並退出初始化
17         Assert.notNull(primarySources, "PrimarySources must not be null");
18         // 獲取main方法中的args,初始化啟動時配置的額外引數集合
19         this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
20         // 判斷專案啟動型別:NONE/SERVLET/REACTIVE
21         this.webApplicationType = WebApplicationType.deduceFromClasspath();
22         // 從 Spring 工廠獲取 Bootstrap Registry Initializers
23         this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
24         // 獲取 Spring 工廠例項 -> 容器上下文相關的初始化
25         setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
26         // 獲取 Spring 工廠例項 -> 設定應用程式監聽器
27         setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
28         // 推匯出主應用程式類,即從當前的棧資訊中尋找main所在主類:com.iot.SpringBootLoveApplication
29         this.mainApplicationClass = deduceMainApplicationClass();
30     }
View Code

1.1、WebApplicationType

WebApplicationType 判斷專案型別。

 public enum WebApplicationType

Spring Boot啟動流程
  1 /*
  2  * Copyright 2012-2019 the original author or authors.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the "License");
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *      https://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 
 17 package org.springframework.boot;
 18 
 19 import org.springframework.util.ClassUtils;
 20 
 21 /**
 22  * An enumeration of possible types of web application.
 23  *
 24  * @author Andy Wilkinson
 25  * @author Brian Clozel
 26  * @since 2.0.0
 27  */
 28 public enum WebApplicationType {
 29 
 30     /**
 31      * The application should not run as a web application and should not start an
 32      * embedded web server.
 33      */
 34     NONE,
 35 
 36     /**
 37      * The application should run as a servlet-based web application and should start an
 38      * embedded servlet web server.
 39      */
 40     SERVLET,
 41 
 42     /**
 43      * The application should run as a reactive web application and should start an
 44      * embedded reactive web server.
 45      */
 46     REACTIVE;
 47 
 48     private static final String[] SERVLET_INDICATOR_CLASSES = {"javax.servlet.Servlet",
 49             "org.springframework.web.context.ConfigurableWebApplicationContext"};
 50 
 51     private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
 52 
 53     private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
 54 
 55     private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
 56 
 57     private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
 58 
 59     private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
 60 
 61     /**
 62      * deduceFromClasspath
 63      * 依次迴圈遍歷當前應用中是否存在相關的類來判斷最終應用的啟動型別
 64      *
 65      * @return
 66      */
 67     static WebApplicationType deduceFromClasspath() {
 68         /**
 69          * REACTIVE:響應式WEB專案
 70          * 若啟動型別為REACTIVE,
 71          * 則類路徑下存在 org.springframework.web.reactive.DispatcherHandler 類
 72          * 並且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
 73          * 兩者指的是SpringMVC/Tomcat和jersey容器
 74          */
 75         if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
 76                 && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
 77             return WebApplicationType.REACTIVE;
 78         }
 79         /**
 80          * NONE:非WEB專案,就是一個最簡單的Springboot應用
 81          * 若啟動型別為NONE
 82          * 則類路徑下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在
 83          */
 84         for (String className : SERVLET_INDICATOR_CLASSES) {
 85             if (!ClassUtils.isPresent(className, null)) {
 86                 return WebApplicationType.NONE;
 87             }
 88         }
 89         /**
 90          * SERVLET:SERVLET WEB 專案
 91          * 若啟動型別為Servlet,則必須有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet
 92          * 和org.springframework.web.context.ConfigurableWebApplicationContext
 93          */
 94         return WebApplicationType.SERVLET;
 95     }
 96 
 97     static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
 98         if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
 99             return WebApplicationType.SERVLET;
100         }
101         if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
102             return WebApplicationType.REACTIVE;
103         }
104         return WebApplicationType.NONE;
105     }
106 
107     private static boolean isAssignable(String target, Class<?> type) {
108         try {
109             return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
110         } catch (Throwable ex) {
111             return false;
112         }
113     }
114 
115 }
View Code

1.2、getBootstrapRegistryInitializersFromSpringFactories

getBootstrapRegistryInitializersFromSpringFactories方法從spring.factories 中獲取 BootstrapRegistryInitializer。

private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() 

Spring Boot啟動流程
 1 private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories(){
 2         ArrayList<BootstrapRegistryInitializer> initializers=new ArrayList<>();
 3         /**
 4          * 從spring.factories 中獲取Bootstrapper集合,
 5          * 然後遍歷轉化為BootstrapRegistryInitializer,再存入 initializers
 6          */
 7         getSpringFactoriesInstances(Bootstrapper.class).stream()
 8         .map((bootstrapper)->((BootstrapRegistryInitializer)bootstrapper::initialize))
 9         .forEach(initializers::add);
10         /**
11          * 從spring.factories 中獲取BootstrapRegistryInitializer集合,再存入 initializers
12          * getSpringFactoriesInstances 該方法在整個啟動流程中會頻繁出現,下面集中介紹
13          */
14         initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
15         return initializers;
16         }
View Code

1.3、setInitializers && setListeners

setInitializers && setListeners 分別是容器上下文初始化 & 監聽器初始化。

容器上下文初始化setInitializers 和監聽器初始化setListeners 都是呼叫了getSpringFactoriesInstances() 方法,從spring.factories中獲取配置。不同的是傳給它的type引數,主要有一下幾種型別。

  • ApplicationContextInitializer.class 上下文相關
  • ApplicationListener.class 監聽器相關
  • SpringApplicationRunListener.class 執行時監聽器
  • SpringBootExceptionReporter.class 異常類相關

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args)
Spring Boot啟動流程
  1 /**
  2      * The location to look for factories.
  3      * <p>Can be present in multiple JAR files.
  4      */
  5     public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  6 
  7 
  8     /**
  9      * 從spring.factories中獲取配置
 10      */
 11     private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
 12         ClassLoader classLoader = getClassLoader();
 13         // Use names and ensure unique to protect against duplicates
 14         /**
 15          * 載入各jar包中的"META-INF/spring.factories"配置
 16          * 其中SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法
 17          * 是獲取spring.factories配置檔案中已經配置的指定型別的的實現類集合
 18          * 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
 19          */
 20         Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
 21         // 通過反射建立這些類
 22         List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
 23         // 排序
 24         AnnotationAwareOrderComparator.sort(instances);
 25         return instances;
 26     }
 27 
 28 
 29     /**
 30      * Load the fully qualified class names of factory implementations of the
 31      * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
 32      * class loader.
 33      * <p>As of Spring Framework 5.3, if a particular implementation class name
 34      * is discovered more than once for the given factory type, duplicates will
 35      * be ignored.
 36      *
 37      * @param factoryType the interface or abstract class representing the factory
 38      * @param classLoader the ClassLoader to use for loading resources; can be
 39      *                    {@code null} to use the default
 40      * @throws IllegalArgumentException if an error occurs while loading factory names
 41      * @see #loadFactories
 42      */
 43     public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
 44         ClassLoader classLoaderToUse = classLoader;
 45         if (classLoaderToUse == null) {
 46             classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
 47         }
 48         String factoryTypeName = factoryType.getName();
 49         return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
 50     }
 51 
 52 
 53     /**
 54      * Springboot自動配置的祕密
 55      * Springboot在啟動時讀取了所有starter jar包裡的META-INF/spring.factories配置檔案,實現了所謂的自動化配置
 56      * 這裡jar包裡的都是預設配置,後續Springboot也會從xml、yaml檔案中的使用者配置去覆蓋同名的配置。
 57      * 另外,這裡的快取配置是儲存在一個map型別的cache中,其中的key鍵對應上面提到的各種Type型別,value就是Type的各種初始jar包裡的同型別Java類。
 58      */
 59     private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
 60         // 獲取相應類載入器中內容
 61         Map<String, List<String>> result = cache.get(classLoader);
 62         // 存在則直接返回類載入器中內容
 63         if (result != null) {
 64             return result;
 65         }
 66         // 不存在則初始化類載入器中內容
 67         result = new HashMap<>();
 68         try {
 69             /**
 70              * 獲取資源 -> META-INF/spring.factories 列表
 71              * 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
 72              */
 73             Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
 74             // 可能存在多個META-INF/spring.factories 檔案,迴圈載入
 75             while (urls.hasMoreElements()) {
 76                 // 獲取 META-INF/spring.factories 檔案URL地址
 77                 URL url = urls.nextElement();
 78                 // 載入資源
 79                 UrlResource resource = new UrlResource(url);
 80                 // 載入資源配置
 81                 Properties properties = PropertiesLoaderUtils.loadProperties(resource);
 82                 // key:value形式迴圈配置
 83                 for (Map.Entry<?, ?> entry : properties.entrySet()) {
 84                     String factoryTypeName = ((String) entry.getKey()).trim();
 85                     // 逗號分隔列表到字串陣列
 86                     String[] factoryImplementationNames =
 87                             StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
 88                     // 迴圈value中子項到列表中
 89                     for (String factoryImplementationName : factoryImplementationNames) {
 90                         result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
 91                                 .add(factoryImplementationName.trim());
 92                     }
 93                 }
 94             }
 95 
 96             // Replace all lists with unmodifiable lists containing unique elements
 97             // 列表去重
 98             result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
 99                     .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
100             // 列表儲存
101             cache.put(classLoader, result);
102         } catch (IOException ex) {
103             throw new IllegalArgumentException("Unable to load factories from location [" +
104                     FACTORIES_RESOURCE_LOCATION + "]", ex);
105         }
106         return result;
107     }
108 
109 
110     /**
111      * 反射建立實現類
112      */
113     private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
114                                                        ClassLoader classLoader, Object[] args, Set<String> names) {
115         List<T> instances = new ArrayList<>(names.size());
116         for (String name : names) {
117             try {
118                 Class<?> instanceClass = ClassUtils.forName(name, classLoader);
119                 Assert.isAssignable(type, instanceClass);
120                 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
121                 T instance = (T) BeanUtils.instantiateClass(constructor, args);
122                 instances.add(instance);
123             } catch (Throwable ex) {
124                 throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
125             }
126         }
127         return instances;
128     }
View Code

1.4、deduceMainApplicationClass

deduceMainApplicationClass 推導主應用程式類。

private Class<?> deduceMainApplicationClass()
Spring Boot啟動流程
 1  /**
 2      * 推導主應用程式類
 3      * @return
 4      */
 5     private Class<?> deduceMainApplicationClass() {
 6         try {
 7             // 獲取當前的棧資訊
 8             StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
 9             for (StackTraceElement stackTraceElement : stackTrace) {
10                 // 獲取main方法所在的類class,此處即com.iot.SpringBootLoveApplication
11                 if ("main".equals(stackTraceElement.getMethodName())) {
12                     return Class.forName(stackTraceElement.getClassName());
13                 }
14             }
15         }
16         catch (ClassNotFoundException ex) {
17             // Swallow and continue
18         }
19         return null;
20     }
View Code

2、run方法

初始化完SpringApplication 就可以執行他的run方法了,也就是啟動方法中的第二階段。

public ConfigurableApplicationContext run(String... args)
Spring Boot啟動流程
 1 /**
 2      * Run the Spring application, creating and refreshing a new
 3      * {@link ApplicationContext}.
 4      *
 5      * @param args the application arguments (usually passed from a Java main method)
 6      * @return a running {@link ApplicationContext}
 7      */
 8     public ConfigurableApplicationContext run(String... args) {
 9         // 啟動一個秒錶計時器,用於統計專案啟動時間
10         StopWatch stopWatch = new StopWatch();
11         stopWatch.start();
12         // 建立啟動上下文物件即spring根容器
13         DefaultBootstrapContext bootstrapContext = createBootstrapContext();
14         // 定義可配置的應用程式上下文變數
15         ConfigurableApplicationContext context = null;
16         /**
17          * 設定jdk系統屬性
18          * headless直譯就是無頭模式,
19          * headless模式的意思就是明確Springboot要在無鼠鍵支援的環境中執行,一般程式也都跑在Linux之類的伺服器上,無鼠鍵支援,這裡預設值是true;
20          */
21         configureHeadlessProperty();
22         /**
23          * 獲取執行監聽器 getRunListeners, 其中也是呼叫了上面說到的getSpringFactoriesInstances 方法
24          * 從spring.factories中獲取配置
25          */
26         SpringApplicationRunListeners listeners = getRunListeners(args);
27         // 啟動監聽器
28         listeners.starting(bootstrapContext, this.mainApplicationClass);
29         try {
30             // 包裝預設應用程式引數,也就是在命令列下啟動應用帶的引數,如--server.port=9000
31             ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
32             //
33             /**
34              * 準備環境 prepareEnvironment 是個硬茬,裡面主要涉及到
35              * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
36              * environmentPrepared、bindToSpringApplication、attach諸多方法可以在下面的例子中檢視
37              */
38             ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
39             // 配置忽略的 bean
40             configureIgnoreBeanInfo(environment);
41             // 列印 SpringBoot 標誌,即啟動的時候在控制檯的圖案logo,可以在src/main/resources下放入名字是banner的自定義檔案
42             Banner printedBanner = printBanner(environment);
43             // 建立 IOC 容器
44             context = createApplicationContext();
45             // 設定一個啟動器,設定應用程式啟動
46             context.setApplicationStartup(this.applicationStartup);
47             // 配置 IOC 容器的基本資訊 (spring容器前置處理)
48             prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
49             /**
50              * 重新整理IOC容器
51              * 這裡會涉及Spring容器啟動、自動裝配、建立 WebServer啟動Web服務即SpringBoot啟動內嵌的 Tomcat
52              */
53             refreshContext(context);
54             /**
55              * 留給使用者自定義容器重新整理完成後的處理邏輯
56              * 重新整理容器後的擴充套件介面(spring容器後置處理)
57              */
58             afterRefresh(context, applicationArguments);
59             // 結束計時器並列印,這就是我們啟動後console的顯示的時間
60             stopWatch.stop();
61             if (this.logStartupInfo) {
62                 // 列印啟動完畢的那行日誌
63                 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
64             }
65             // 釋出監聽應用上下文啟動完成(發出啟動結束事件),所有的執行監聽器呼叫 started() 方法
66             listeners.started(context);
67             // 執行runner,遍歷所有的 runner,呼叫 run 方法
68             callRunners(context, applicationArguments);
69         } catch (Throwable ex) {
70             // 異常處理,如果run過程發生異常
71             handleRunFailure(context, ex, listeners);
72             throw new IllegalStateException(ex);
73         }
74 
75         try {
76             // 所有的執行監聽器呼叫 running() 方法,監聽應用上下文
77             listeners.running(context);
78         } catch (Throwable ex) {
79             // 異常處理
80             handleRunFailure(context, ex, null);
81             throw new IllegalStateException(ex);
82         }
83         // 返回最終構建的容器物件
84         return context;
85     }

View Code

2.1、configureHeadlessProperty

configureHeadlessProperty 設定headless無頭模式。

private void configureHeadlessProperty()
 1     private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
 2     
 3     /**
 4      * headless直譯就是無頭模式,
 5      * headless模式的意思就是明確Springboot要在無鼠鍵支援的環境中執行,一般程式也都跑在Linux之類的伺服器上,無鼠鍵支援,這裡預設值是true;
 6      */
 7     private void configureHeadlessProperty() {
 8         // SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
 9         System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
10                 System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
11     }

2.2、prepareEnvironment

prepareEnvironment 準備環境是個硬茬,裡面主要涉及到getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfilesenvironmentPrepared、bindToSpringApplication、attach諸多方法。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) 

Spring Boot啟動流程
  1     /**
  2      * 準備環境
  3      *
  4      * @param listeners
  5      * @param bootstrapContext
  6      * @param applicationArguments
  7      * @return
  8      */
  9     private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
 10                                                        DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
 11         // Create and configure the environment 建立和配置環境
 12         // 根據專案型別建環境ConfigurableEnvironment
 13         ConfigurableEnvironment environment = getOrCreateEnvironment();
 14         // 從環境中獲取並設定 PropertySources 和 activeProfiles
 15         configureEnvironment(environment, applicationArguments.getSourceArgs());
 16         // 把 PropertySources 設定在自己PropertySources的第一個位置
 17         ConfigurationPropertySources.attach(environment);
 18         /**
 19          * 執行監聽器呼叫
 20          * 廣播事件,listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)
 21          * 釋出事件通知所有的監聽器當前環境準備完成
 22          */
 23         listeners.environmentPrepared(bootstrapContext, environment);
 24         // 移動 defaultProperties 屬性源到環境中的最後一個源
 25         DefaultPropertiesPropertySource.moveToEnd(environment);
 26         // 斷言 拋異常
 27         Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
 28                 "Environment prefix cannot be set via properties.");
 29         // 與容器繫結當前環境
 30         bindToSpringApplication(environment);
 31         // 若非web環境,將環境轉換成StandardEnvironment
 32         if (!this.isCustomEnvironment) {
 33             environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
 34                     deduceEnvironmentClass());
 35         }
 36         // 配置PropertySources對它自己的遞迴依賴
 37         ConfigurationPropertySources.attach(environment);
 38         return environment;
 39     }
 40 
 41 
 42     /**
 43      * 獲取或建立環境Environment
 44      *
 45      * @return
 46      */
 47     private ConfigurableEnvironment getOrCreateEnvironment() {
 48         // 存在則直接返回
 49         if (this.environment != null) {
 50             return this.environment;
 51         }
 52         /**
 53          * 根據webApplicationType建立對應的Environment
 54          */
 55         switch (this.webApplicationType) {
 56             // SERVLET WEB 專案
 57             case SERVLET:
 58                 return new ApplicationServletEnvironment();
 59             // REACTIVE:響應式WEB專案
 60             case REACTIVE:
 61                 return new ApplicationReactiveWebEnvironment();
 62             // 非WEB專案,就是一個最簡單的Springboot應用
 63             default:
 64                 return new ApplicationEnvironment();
 65         }
 66     }
 67 
 68     /**
 69      * 從環境中獲取並設定 PropertySources 和 activeProfiles
 70      * 將配置任務按順序委託給configurePropertySources和configureProfiles
 71      * Template method delegating to
 72      * {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
 73      * {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
 74      * Override this method for complete control over Environment customization, or one of
 75      * the above for fine-grained control over property sources or profiles, respectively.
 76      *
 77      * @param environment this application's environment
 78      * @param args        arguments passed to the {@code run} method
 79      * @see #configureProfiles(ConfigurableEnvironment, String[])
 80      * @see #configurePropertySources(ConfigurableEnvironment, String[])
 81      */
 82     protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
 83         if (this.addConversionService) {
 84             environment.setConversionService(new ApplicationConversionService());
 85         }
 86         // 配置PropertySources
 87         configurePropertySources(environment, args);
 88         // 配置Profiles
 89         configureProfiles(environment, args);
 90     }
 91 
 92     /**
 93      * 配置PropertySources
 94      * Add, remove or re-order any {@link PropertySource}s in this application's
 95      * environment.
 96      *
 97      * @param environment this application's environment
 98      * @param args        arguments passed to the {@code run} method
 99      * @see #configureEnvironment(ConfigurableEnvironment, String[])
100      */
101     protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
102         MutablePropertySources sources = environment.getPropertySources();
103         // 初始化 defaultProperties
104         if (!CollectionUtils.isEmpty(this.defaultProperties)) {
105             // 存在的話將其放到最後位置
106             DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
107         }
108         /**
109          * 存在命令列引數,則解析它並封裝進SimpleCommandLinePropertySource物件
110          * 同時將此物件放到sources的第一位置(優先順序最高)
111          */
112         if (this.addCommandLineProperties && args.length > 0) {
113             String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
114             if (sources.contains(name)) {
115                 PropertySource<?> source = sources.get(name);
116                 CompositePropertySource composite = new CompositePropertySource(name);
117                 composite.addPropertySource(
118                         new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
119                 composite.addPropertySource(source);
120                 sources.replace(name, composite);
121             } else {
122                 // 放到首位
123                 sources.addFirst(new SimpleCommandLinePropertySource(args));
124             }
125         }
126     }
127 
128     /**
129      * 配置Profiles
130      *
131      * @param environment
132      * @param args
133      */
134     protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
135         /**
136          * 保證environment的activeProfiles屬性被初始化了。從PropertySources中查詢spring.profiles.active屬性
137          * 存在則將其值新增activeProfiles集合中。
138          * 配置應用環境中的哪些配置檔案處於啟用狀態(或預設啟用)
139          * 可以通過spring.profiles.active屬性在配置檔案處理期間啟用其他配置檔案
140          * 就是我們專案中通常配置的dev、sit、prod等環境配置資訊設定哪些Profiles是啟用的。
141          */
142         environment.getActiveProfiles(); // ensure they are initialized
143         // But these ones should go first (last wins in a property key clash)
144         // 如果存在其他的Profiles,則將這些Profiles放到第一的位置
145         Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
146         profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
147         environment.setActiveProfiles(StringUtils.toStringArray(profiles));
148     }
149 
150     /**
151      * 執行監聽器呼叫
152      *
153      * @param bootstrapContext
154      * @param environment
155      */
156     void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
157         doWithListeners("spring.boot.application.environment-prepared",
158                 (listener) -> listener.environmentPrepared(bootstrapContext, environment));
159     }
160 
161     /**
162      * 執行監聽器呼叫
163      * Called once the environment has been prepared, but before the
164      * {@link ApplicationContext} has been created.
165      *
166      * @param environment the environment
167      * @deprecated since 2.4.0 for removal in 2.6.0 in favor of
168      * {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
169      */
170     @Deprecated
171     default void environmentPrepared(ConfigurableEnvironment environment) {
172         for (SpringApplicationRunListener listener : this.listeners) {
173             // 廣播ApplicationEnvironmentPreparedEvent事件,後面再看
174             listener.environmentPrepared(environment);
175         }
176     }
177 
178     /**
179      * 與容器繫結當前環境
180      * Bind the environment to the {@link SpringApplication}.
181      *
182      * @param environment the environment to bind
183      */
184     protected void bindToSpringApplication(ConfigurableEnvironment environment) {
185         try {
186             // 將environment繫結到SpringApplication
187             Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
188         } catch (Exception ex) {
189             throw new IllegalStateException("Cannot bind to SpringApplication", ex);
190         }
191     }
192 
193     /**
194      * 配置PropertySources對它自己的遞迴依賴
195      * Attach a {@link ConfigurationPropertySource} support to the specified
196      * {@link Environment}. Adapts each {@link PropertySource} managed by the environment
197      * to a {@link ConfigurationPropertySource} and allows classic
198      * {@link PropertySourcesPropertyResolver} calls to resolve using
199      * {@link ConfigurationPropertyName configuration property names}.
200      * <p>
201      * The attached resolver will dynamically track any additions or removals from the
202      * underlying {@link Environment} property sources.
203      *
204      * @param environment the source environment (must be an instance of
205      *                    {@link ConfigurableEnvironment})
206      * @see #get(Environment)
207      */
208     public static void attach(Environment environment) {
209         // 判斷environment是否是ConfigurableEnvironment的例項
210         Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
211         // 從environment獲取PropertySources
212         MutablePropertySources sources = ((ConfigurableEnvironment) environment)
213                 .getPropertySources();
214         PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
215         if (attached != null && attached.getSource() != sources) {
216             sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
217             attached = null;
218         }
219         if (attached == null) {
220             // 將sources封裝成ConfigurationPropertySourcesPropertySource物件,並把這個物件放到sources的第一位置
221             sources.addFirst(new ConfigurationPropertySourcesPropertySource(
222                     ATTACHED_PROPERTY_SOURCE_NAME,
223                     new SpringConfigurationPropertySources(sources)));
224         }
225     }
View Code

2.3、printBanner

printBanner 列印SpringBoot標誌。printBanner(environment)方法就是列印Banner,Banner就是專案啟動時看到的那個logo。在工程專案src/main/resources路徑下下放入名字是banner的檔案,字尾後可以是SpringApplicationBannerPrinter.java類裡的{ "gif", "jpg", "png" },或者是txt、圖片也可以的,但是圖片列印時會字元化,而不是列印圖片本身。自定義banner連結

 private Banner printBanner(ConfigurableEnvironment environment)

Spring Boot啟動流程
 1     /**
 2      * 列印SpringBoot標誌
 3      * banner的輸出預設有三種種模式,LOG、CONSOLE、OFF。
 4      * 1. LOG:將banner資訊輸出到日誌檔案。
 5      * 2. CONSOLE:將banner資訊輸出到控制檯。
 6      * 3. OFF:禁用banner的資訊輸出。
 7      *
 8      * @param environment
 9      * @return
10      */
11     private Banner printBanner(ConfigurableEnvironment environment) {
12         // 判斷Banner的模式是否關閉,如果關閉直接返回。
13         if (this.bannerMode == Banner.Mode.OFF) {
14             return null;
15         }
16         ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
17                 : new DefaultResourceLoader(null);
18         // 建立SpringApplicationBannerPrinter 列印類
19         SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
20         // LOG:將banner資訊輸出到日誌檔案
21         if (this.bannerMode == Mode.LOG) {
22             return bannerPrinter.print(environment, this.mainApplicationClass, logger);
23         }
24         //banner沒有關閉且沒有指定是寫到log檔案中 將banner資訊輸出到控制檯
25         return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
26     }
27 
28     /**
29      * 列印
30      *
31      * @param environment
32      * @param sourceClass
33      * @param logger
34      * @return
35      */
36     Banner print(Environment environment, Class<?> sourceClass, Log logger) {
37         // 獲取banner內容
38         Banner banner = getBanner(environment);
39         try {
40             logger.info(createStringFromBanner(banner, environment, sourceClass));
41         } catch (UnsupportedEncodingException ex) {
42             logger.warn("Failed to create String for banner", ex);
43         }
44         return new PrintedBanner(banner, sourceClass);
45     }
46 
47     /**
48      * 獲取banner內容
49      *
50      * @param environment
51      * @return
52      */
53     private Banner getBanner(Environment environment) {
54         Banners banners = new Banners();
55         // 圖片型別的banner內容
56         banners.addIfNotNull(getImageBanner(environment));
57         // 文字型別的banner內容
58         banners.addIfNotNull(getTextBanner(environment));
59         if (banners.hasAtLeastOneBanner()) {
60             return banners;
61         }
62         if (this.fallbackBanner != null) {
63             return this.fallbackBanner;
64         }
65         return DEFAULT_BANNER;
66     }
67 
68     static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
69     static final String DEFAULT_BANNER_LOCATION = "banner.txt";
70 
71     /**
72      * 文字型別的banner內容獲取
73      *
74      * @param environment
75      * @return
76      */
77     private Banner getTextBanner(Environment environment) {
78         /**
79          * 拿到自定義配置的banner檔案地址
80          * BANNER_LOCATION_PROPERTY = "spring.banner.location"
81          * DEFAULT_BANNER_LOCATION = "banner.txt";
82          */
83         String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
84         Resource resource = this.resourceLoader.getResource(location);
85         try {
86             if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
87                 return new ResourceBanner(resource);
88             }
89         } catch (IOException ex) {
90             // Ignore
91         }
92         return null;
93     }
View Code

2.4、createApplicationContext

createApplicationContext建立IOC容器。

protected ConfigurableApplicationContext createApplicationContext() 

Spring Boot啟動流程
 1     /**
 2      * 建立 IOC 容器
 3      * A default {@link ApplicationContextFactory} implementation that will create an
 4      * appropriate context for the {@link WebApplicationType}.
 5      */
 6     ApplicationContextFactory DEFAULT = (webApplicationType) -> {
 7         try {
 8             // 根據當前應用的型別建立 IOC 容器
 9             switch (webApplicationType) {
10                 // Web 應用環境對應 AnnotationConfigServletWebServerApplicationContext
11                 case SERVLET:
12                     return new AnnotationConfigServletWebServerApplicationContext();
13                 // 響應式程式設計對應 AnnotationConfigReactiveWebServerApplicationContext
14                 case REACTIVE:
15                     return new AnnotationConfigReactiveWebServerApplicationContext();
16                 // 預設為 Spring 環境 AnnotationConfigApplicationContext
17                 default:
18                     return new AnnotationConfigApplicationContext();
19             }
20         }
21         catch (Exception ex) {
22             throw new IllegalStateException("Unable create a default ApplicationContext instance, "
23                     + "you may need a custom ApplicationContextFactory", ex);
24         }
25     };
26 
27     /**
28      * 設定一個啟動器
29      * Set the {@link ApplicationStartup} for this application context.
30      * <p>This allows the application context to record metrics
31      * during startup.
32      * @param applicationStartup the new context event factory
33      * @since 5.3
34      */
35     void setApplicationStartup(ApplicationStartup applicationStartup);
View Code

2.5、prepareContext

prepareContext 配置 IOC 容器的基本資訊。

private void prepareContext(引數此處省略)

Spring Boot啟動流程
 1     /**
 2      * 準備IOC容器基本資訊
 3      * @param bootstrapContext
 4      * @param context
 5      * @param environment
 6      * @param listeners
 7      * @param applicationArguments
 8      * @param printedBanner
 9      */
10     private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
11                                 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
12                                 ApplicationArguments applicationArguments, Banner printedBanner) {
13         // 設定容器環境,包括各種變數
14         context.setEnvironment(environment);
15         /**
16          * 後置處理流程
17          * 設定IOC容器的 bean 生成器和資源載入器
18          */
19         postProcessApplicationContext(context);
20         /**
21          * 獲取所有的初始化器呼叫 initialize() 方法進行初始化
22          * 執行容器中的ApplicationContextInitializer(包括從 spring.factories和自定義的例項)初始化
23          */
24         applyInitializers(context);
25         /**
26          * 觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
27          * 所有的執行監聽器呼叫 environmentPrepared() 方法,EventPublishingRunListener 釋出事件通知 IOC 容器準備完成
28          */
29         listeners.contextPrepared(context);
30         bootstrapContext.close(context);
31         // 列印啟動日誌
32         if (this.logStartupInfo) {
33             logStartupInfo(context.getParent() == null);
34             logStartupProfileInfo(context);
35         }
36         // Add boot specific singleton beans
37         ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
38         // 註冊新增特定的單例bean
39         beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
40         if (printedBanner != null) {
41             beanFactory.registerSingleton("springBootBanner", printedBanner);
42         }
43         if (beanFactory instanceof DefaultListableBeanFactory) {
44             ((DefaultListableBeanFactory) beanFactory)
45                     .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
46         }
47         if (this.lazyInitialization) {
48             context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
49         }
50         // Load the sources
51         // 載入所有資源
52         Set<Object> sources = getAllSources();
53         // 斷言資源費控
54         Assert.notEmpty(sources, "Sources must not be empty");
55         // 建立BeanDefinitionLoader,載入啟動類,將啟動類注入容器
56         load(context, sources.toArray(new Object[0]));
57         // 觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
58         listeners.contextLoaded(context);
59     }
View Code

2.6、refresh

refresh 重新整理應用上下文,即重新整理Spring上下文資訊refreshContext。這裡會涉及Spring容器啟動、SpringBoot自動裝配、建立 WebServer啟動Web服務即SpringBoot啟動內嵌的 Tomcat。

private void refreshContext(ConfigurableApplicationContext context) 

Spring Boot啟動流程
  1     /**
  2      * 重新整理應用上下文
  3      *
  4      * @param context
  5      */
  6     private void refreshContext(ConfigurableApplicationContext context) {
  7         if (this.registerShutdownHook) {
  8             // 判斷是否註冊關閉的鉤子,是則註冊鉤子
  9             shutdownHook.registerApplicationContext(context);
 10         }
 11         refresh(context);
 12     }
 13 
 14     /**
 15      * Refresh the underlying {@link ApplicationContext}.
 16      *
 17      * @param applicationContext the application context to refresh
 18      */
 19     protected void refresh(ConfigurableApplicationContext applicationContext) {
 20         applicationContext.refresh();
 21     }
 22 
 23     /**
 24      * 重新整理IOC容器
 25      *
 26      * @throws BeansException
 27      * @throws IllegalStateException
 28      */
 29     @Override
 30     public void refresh() throws BeansException, IllegalStateException {
 31         synchronized (this.startupShutdownMonitor) {
 32             StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
 33 
 34             // Prepare this context for refreshing. 準備重新整理上下文
 35             prepareRefresh();
 36 
 37             // Tell the subclass to refresh the internal bean factory. 通知子類重新整理內部工廠
 38             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 39 
 40             // Prepare the bean factory for use in this context. 準備Bean工廠
 41             prepareBeanFactory(beanFactory);
 42 
 43             try {
 44                 // Allows post-processing of the bean factory in context subclasses.
 45                 // 允許在上下文子類中對bean工廠進行後處理,這部分涉及Web伺服器的啟動,如servlet
 46                 postProcessBeanFactory(beanFactory);
 47 
 48                 StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
 49                 // Invoke factory processors registered as beans in the context.
 50                 // 呼叫在上下文中註冊為 bean 的工廠處理器
 51                 invokeBeanFactoryPostProcessors(beanFactory);
 52 
 53                 // Register bean processors that intercept bean creation. 註冊攔截 bean 建立的 bean 處理器
 54                 registerBeanPostProcessors(beanFactory);
 55                 beanPostProcess.end();
 56 
 57                 // Initialize message source for this context. 初始化此上下文的訊息源
 58                 initMessageSource();
 59 
 60                 // Initialize event multicaster for this context. 為該上下文初始化事件多播器
 61                 initApplicationEventMulticaster();
 62 
 63                 // Initialize other special beans in specific context subclasses. 初始化特定上下文子類中的其他特殊 bean
 64                 /**
 65                  * SpringBoot 一鍵啟動web工程的關鍵方法
 66                  * 建立 WebServer啟動Web服務
 67                  * SpringBoot啟動內嵌的 Tomcat 首先要在pom檔案配置內嵌容器為tomcat
 68                  * SpringBoot 嵌入式 Servlet 容器,預設支援的 webServe:Tomcat、Jetty、Undertow
 69                  *          <exclusion>
 70                  *             <groupId>org.springframework.boot</groupId>
 71                  *             <artifactId>spring-boot-starter-tomcat</artifactId>
 72                  *         </exclusion>
 73                  */
 74                 onRefresh();
 75 
 76                 // Check for listener beans and register them. 檢查偵聽器 bean 並註冊
 77                 registerListeners();
 78 
 79                 // Instantiate all remaining (non-lazy-init) singletons. 例項化所有剩餘的(非延遲初始化)單例
 80                 finishBeanFactoryInitialization(beanFactory);
 81 
 82                 // Last step: publish corresponding event. 釋出事件
 83                 finishRefresh();
 84             } catch (BeansException ex) {
 85                 if (logger.isWarnEnabled()) {
 86                     logger.warn("Exception encountered during context initialization - " +
 87                             "cancelling refresh attempt: " + ex);
 88                 }
 89 
 90                 // Destroy already created singletons to avoid dangling resources.  銷燬bean
 91                 destroyBeans();
 92 
 93                 // Reset 'active' flag.
 94                 cancelRefresh(ex);
 95 
 96                 // Propagate exception to caller.
 97                 throw ex;
 98             } finally {
 99                 // Reset common introspection caches in Spring's core, since we
100                 // might not ever need metadata for singleton beans anymore...
101                 resetCommonCaches();
102                 contextRefresh.end();
103             }
104         }
105     }
View Code

2.7、onRefresh

onRefresh方法中建立WebServer、建立Tomcat物件,是SpringBoot一鍵啟動web工程的關鍵。SpringBoot 嵌入式 Servlet 容器,預設支援的 webServe:Tomcat、Jetty、Undertow,但要在POM檔案加入tomcat相關配置。

Spring Boot啟動流程
 1 <dependency>
 2     <groupId>org.springframework.boot</groupId>
 3     <artifactId>spring-boot-starter-web</artifactId>
 4     <exclusions>
 5         <exclusion> <!--必須要把內嵌的 Tomcat 容器-->
 6             <groupId>org.springframework.boot</groupId>
 7             <artifactId>spring-boot-starter-tomcat</artifactId>
 8         </exclusion>
 9     </exclusions>
10 </dependency>
11 <dependency>
12     <groupId>org.springframework.boot</groupId>
13     <artifactId>spring-boot-starter-jetty</artifactId>
14 </dependency>
View Code

protected void onRefresh() throws BeansException 

Spring Boot啟動流程
  1     /**
  2      * 建立 WebServer啟動Web服務
  3      */
  4     @Override
  5     protected void onRefresh() {
  6         // 初始化給定應用程式上下文的主題資源
  7         super.onRefresh();
  8         try {
  9             // 建立Web 服務
 10             createWebServer();
 11         }
 12         catch (Throwable ex) {
 13             throw new ApplicationContextException("Unable to start web server", ex);
 14         }
 15     }
 16 
 17     /**
 18      * super.onRefresh();
 19      * Initialize the theme capability.
 20      */
 21     @Override
 22     protected void onRefresh() {
 23         /**
 24          * 初始化給定應用程式上下文的主題資源,自動檢測一個名為“themeSource”的bean。
 25          * 如果沒有這樣的,將使用預設的(空的)ThemeSource。
 26          */
 27         this.themeSource = UiApplicationContextUtils.initThemeSource(this);
 28     }
 29 
 30     /**
 31      * 建立Web 服務
 32      */
 33     private void createWebServer() {
 34         WebServer webServer = this.webServer;
 35         ServletContext servletContext = getServletContext();
 36         if (webServer == null && servletContext == null) {
 37             // 獲取web server
 38             StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
 39             // 獲取建立容器的工廠
 40             ServletWebServerFactory factory = getWebServerFactory();
 41             createWebServer.tag("factory", factory.getClass().toString());
 42             /**
 43              * 獲取 tomcat 、Jetty 或 Undertow 容器
 44              * 從 getWebServer 方法點進去,找到 TomcatServletWebServerFactory 的實現方法,
 45              * 與之對應的還有 Jetty 和 Undertow。這裡配置了基本的聯結器、引擎、虛擬站點等配置。
 46              * 自動配置類 ServletWebServerFactoryAutoConfiguration 匯入了 ServletWebServerFactoryConfiguration(配置類),
 47              * 根據條件裝配判斷系統中到底匯入了哪個 Web 伺服器的包,建立出伺服器並啟動
 48              * 預設是 web-starter 匯入 tomcat 包,容器中就有 TomcatServletWebServerFactory,建立出 Tomcat 伺服器並啟動
 49              */
 50             this.webServer = factory.getWebServer(getSelfInitializer());
 51             createWebServer.end();
 52             getBeanFactory().registerSingleton("webServerGracefulShutdown",
 53                     new WebServerGracefulShutdownLifecycle(this.webServer));
 54             getBeanFactory().registerSingleton("webServerStartStop",
 55                     new WebServerStartStopLifecycle(this, this.webServer));
 56         }
 57         else if (servletContext != null) {
 58             try {
 59                 // 啟動web server
 60                 getSelfInitializer().onStartup(servletContext);
 61             }
 62             catch (ServletException ex) {
 63                 throw new ApplicationContextException("Cannot initialize servlet context", ex);
 64             }
 65         }
 66         initPropertySources();
 67     }
 68 
 69     /**
 70      * 獲取tomcat 容器
 71      * 配置了基本的聯結器、引擎、虛擬站點等配置
 72      * @param initializers
 73      * @return
 74      */
 75     @Override
 76     public WebServer getWebServer(ServletContextInitializer... initializers) {
 77         if (this.disableMBeanRegistry) {
 78             Registry.disableRegistry();
 79         }
 80         /**
 81          * 建立了Tomcat物件,並設定引數
 82          */
 83         Tomcat tomcat = new Tomcat();
 84         // 設定工作忙碌
 85         File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
 86         tomcat.setBaseDir(baseDir.getAbsolutePath());
 87         // 初始化tomcat 連線,預設NIO
 88         Connector connector = new Connector(this.protocol);
 89         connector.setThrowOnFailure(true);
 90         tomcat.getService().addConnector(connector);
 91         customizeConnector(connector);
 92         // 配置基本的聯結器、引擎、虛擬站點
 93         tomcat.setConnector(connector);
 94         // 設定自動部署為false
 95         tomcat.getHost().setAutoDeploy(false);
 96         configureEngine(tomcat.getEngine());
 97         for (Connector additionalConnector : this.additionalTomcatConnectors) {
 98             tomcat.getService().addConnector(additionalConnector);
 99         }
100         // 準備上下文
101         prepareContext(tomcat.getHost(), initializers);
102         // 返回TomcatWebServer服務
103         return getTomcatWebServer(tomcat);
104     }
105 
106     /**
107      * Create a new {@link TomcatWebServer} instance.
108      * @param tomcat the underlying Tomcat server
109      * @param autoStart if the server should be started
110      * @param shutdown type of shutdown supported by the server
111      * @since 2.3.0
112      */
113     public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
114         Assert.notNull(tomcat, "Tomcat Server must not be null");
115         this.tomcat = tomcat;
116         this.autoStart = autoStart;
117         this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
118         // 初始化Tomcat
119         initialize();
120     }
121 
122     /**
123      * 初始化Tomcat
124      * @throws WebServerException
125      */
126     private void initialize() throws WebServerException {
127         logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
128         synchronized (this.monitor) {
129             try {
130                 addInstanceIdToEngineName();
131 
132                 Context context = findContext();
133                 context.addLifecycleListener((event) -> {
134                     if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
135                         // Remove service connectors so that protocol binding doesn't
136                         // happen when the service is started.
137                         removeServiceConnectors();
138                     }
139                 });
140 
141                 // Start the server to trigger initialization listeners
142                 this.tomcat.start();
143 
144                 // We can re-throw failure exception directly in the main thread
145                 rethrowDeferredStartupExceptions();
146 
147                 try {
148                     ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
149                 }
150                 catch (NamingException ex) {
151                     // Naming is not enabled. Continue
152                 }
153 
154                 // Unlike Jetty, all Tomcat threads are daemon threads. We create a
155                 // blocking non-daemon to stop immediate shutdown
156                 startDaemonAwaitThread();
157             }
158             catch (Exception ex) {
159                 stopSilently();
160                 destroySilently();
161                 throw new WebServerException("Unable to start embedded Tomcat", ex);
162             }
163         }
164     }
View Code

2.8、afterRefresh

afterReftesh() 重新整理後處理,是個一空實現的擴充套件介面,留著後期擴充套件如使用者自定義容器重新整理後的處理邏輯。

2.9、停止計時並列印啟動完畢相關日誌

2.10、started

started 釋出監聽應用啟動事件。

void started(ConfigurableApplicationContext context)  

Spring Boot啟動流程
 1     /**
 2      * 釋出應用監聽啟動事件
 3      * @param context
 4      */
 5     void started(ConfigurableApplicationContext context) {
 6         // listener.started(context) 中交由context.publishEvent()方法處理
 7         // 實際上是傳送了一個ApplicationStartedEvent的事件
 8         doWithListeners("spring.boot.application.started", (listener) -> listener.started(context));
 9     }
10 
11     /**
12      * 釋出應用啟動事件ApplicationStartedEvent.
13      * @param context
14      */
15     @Override
16     public void started(ConfigurableApplicationContext context) {
17         context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
18         AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
19     }
View Code

2.11、callRunners

callRunners,執行runner主要是遍歷所有的runner獲取所有的ApplicationRuner 和CommandLineRunner 來初始化引數,其中callRuner(是一個回撥函式)。

private void callRunners(ApplicationContext context, ApplicationArguments args) 

Spring Boot啟動流程
 1     /**
 2      * 執行runner 初始化引數
 3      * @param context
 4      * @param args
 5      */
 6     private void callRunners(ApplicationContext context, ApplicationArguments args) {
 7         List<Object> runners = new ArrayList<>();
 8         runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
 9         runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
10         AnnotationAwareOrderComparator.sort(runners);
11         // 遍歷所有runner
12         for (Object runner : new LinkedHashSet<>(runners)) {
13             if (runner instanceof ApplicationRunner) {
14                 /**
15                  * 回撥函式callRunner 處理 ApplicationRunner
16                  */
17                 callRunner((ApplicationRunner) runner, args);
18             }
19             if (runner instanceof CommandLineRunner) {
20                 /**
21                  * 回撥函式callRunner 處理 CommandLineRunner
22                  */
23                 callRunner((CommandLineRunner) runner, args);
24             }
25         }
26     }
View Code

2.12、running

running 釋出上下文完成準備事件,listeners.running() 釋出上下文完成準備事件同前面的listeners.started() 方法一樣,都是釋出了一個running事件,程式碼也相同。

void running(ConfigurableApplicationContext context) 

Spring Boot啟動流程
 1     /**
 2      * 釋出上下文完成準備事件
 3      * 與上面的 listeners.started() 方法一樣
 4      * @param context
 5      */
 6     void running(ConfigurableApplicationContext context) {
 7         // listener.started(context) 中交由context.publishEvent()方法處理
 8         // 實際上是傳送了一個ApplicationStartedEvent的事件
 9         doWithListeners("spring.boot.application.running", (listener) -> listener.running(context));
10     }
11 
12     /**
13      * 釋出上下文完成準備事件
14      * Called immediately before the run method finishes, when the application context has
15      * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
16      * {@link ApplicationRunner ApplicationRunners} have been called.
17      * @param context the application context.
18      * @since 2.0.0
19      */
20     @Override
21     public void running(ConfigurableApplicationContext context) {
22         context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
23         AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
24     }
View Code

這也是SpringBoot啟動流程兩大過程中的第二階段的啟動方法run中最後一個方法了,該方法執行完成後,SpringApplication的run(String... args)方法執行結束,至此Spring Boot的ApplicationContext 啟動結束。

四、總結

SpringBoot啟動流程總結就是下面兩張圖片,一個建立SpringApplication例項,一個執行run方法,所有的貓膩都在其中。

 

 

 

 

 

君生我未生

    君生我已老

        君恨我生遲

                我恨君生早

 

 

 

 

 

相關文章