主頁 > 後端開發 > Spring Boot啟動流程

Spring Boot啟動流程

2022-04-05 07:10:17 後端開發

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)
 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

  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() 

 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)
  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()
 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)
 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) 

  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)

 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() 

 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(引數此處省略)

 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) 

  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相關配置,

 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 

  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)  

 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) 

 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) 

 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方法,所有的貓膩都在其中,

 

 

 

 

 

君生我未生

    君生我已老

        君恨我生遲

                我恨君生早

 

 

 

 

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/455573.html

標籤:其他

上一篇:使用pyttsx3實作簡單tts服務

下一篇:Java學習筆記(韓順平教育 b站有課程)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more