Spring Boot啟動流程

来源:https://www.cnblogs.com/taojietaoge/archive/2022/04/04/16075820.html
-Advertisement-
Play Games

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 方法重載的規則,main方法也可以重載,JVM在調用main方法的時候傳入了一個空字元串數組 構成方法重載的規則: 1.首先必須是在同一個類中 2.函數名相同 3.形參列表不同(形參個數,類型,順序不同都可以構成方法重載) 只有返回值不同不能構成方法重載。 只有訪問修飾符不同也不能構成方法重載 ma ...
  • 有沒有小火伴是特別喜歡玩五子棋的,我記得我初中是特別喜歡的。於是,我今天就用Python給大家寫了一個黑白棋游戲。代碼放在下麵了。 01、繪製棋盤 Python學習交流Q群:906715085### import pygame, sys, random from pygame.locals impo ...
  • 多線程方式實現(1) 方式1:繼承Thread類。 * 步驟 * A:自定義類MyThread繼承Thread類。 * B:MyThread類裡面重寫run()? * 為什麼是run()方法呢? * C:創建對象 * D:啟動線程 */ package cn.itcast_02; /* * 該類要重 ...
  • Windows微信清理工具v.3.0.2 更新內容: 1、清理完成時可顯示刪除了哪些文件。 軟體截圖: 所有版本及源碼下載鏈接: 百度網盤:https://pan.baidu.com/s/1OSIpvZEOvd-lVZb_82BnKg 提取碼:ylzh (請下載v.3.0.2版本) 阿裡雲盤:htt ...
  • 假期就要好好利用,不然怎麼捲死同齡人,今天給大家分享替換字元串。 FlashText 演算法是由 Vikash Singh 於2017年發表的大規模關鍵詞替換演算法,這個演算法的時間複雜度僅由文本長度(N)決定,演算法時間複雜度為O(N) 而對於正則表達式的替換,演算法時間複雜度還需要考慮被替換的關鍵詞數量( ...
  • 說明 當前的版本為 MyBatis 3.5.9 MyBatis Plus 3.5.1 Spring Boot 2.6.4 Postgresql 42.3.3 與 Spring Boot 結合使用 MyBatis 以下說明Spring Boot下完全以註解方式進行的配置, 覆蓋大部分功能場景 項目依賴 ...
  • SpringBoot+vue練手項目 博客系統 項目使用技術 : springboot + mybatisplus+redis+mysql 1. 工程搭建 前端的工程地址: 鏈接:https://pan.baidu.com/s/1cg_11ctsbbq_WM9BnpcOaQ 提取碼:nrun npm ...
  • Java學習地址 視頻地址 真的很推薦大家去聽老韓的課(非常細誰聽誰知道):【零基礎 快速學Java】韓順平 零基礎30天學會Java_嗶哩嗶哩_bilibili 使用心得 說一下我使用的心得: 1. 學過C和C++,作為軟體工程的學生大一下學期的 Java 屬實是混過去的(60飄過),當我真正想開 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...