Spring Boot啟動流程 君生我未生,君生我已老。君恨我生遲,我恨君生早。 一、簡述 Spring Boot啟動流程分析使用版本SpringBoot VERSION:版本 2.5.5-SNAPSHOT。 Spring Boot項目最簡單的Application啟動類。 可以看出Applicat ...
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[] sta