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 }
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 }
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 }
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 }
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 }
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 }
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 }
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);
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 }
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 }
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>
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 }
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 }
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 }
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 }
這也是SpringBoot啟動流程兩大過程中的第二階段的啟動方法run中最后一個方法了,該方法執行完成后,SpringApplication的run(String... args)方法執行結束,至此Spring Boot的ApplicationContext 啟動結束。
四、總結
SpringBoot啟動流程總結就是下面兩張圖片,一個創建SpringApplication實例,一個執行run方法,所有的貓膩都在其中。
君生我未生
君生我已老
君恨我生遲
我恨君生早