SpringBoot之旅第六篇-啟動原理及自定義starter

来源:https://www.cnblogs.com/yuanqinnan/archive/2019/04/28/10784508.html
-Advertisement-
Play Games

一、引言 SpringBoot的一大優勢就是Starter,由於SpringBoot有很多開箱即用的Starter依賴,使得我們開發變得簡單,我們不需要過多的關註框架的配置。 在日常開發中,我們也會自定義一些Starter,特別是現在微服務框架,我們一個項目分成了多個單體項目,而這些單體項目中會引用 ...


一、引言

SpringBoot的一大優勢就是Starter,由於SpringBoot有很多開箱即用的Starter依賴,使得我們開發變得簡單,我們不需要過多的關註框架的配置。

在日常開發中,我們也會自定義一些Starter,特別是現在微服務框架,我們一個項目分成了多個單體項目,而這些單體項目中會引用公司的一些組件,這個時候我們定義Starter,可以使這些單體項目快速搭起,我們只需要關註業務開發。

在此之前我們再深入的瞭解下SpringBoot啟動原理。而後再將如何自定義starter。

二、 啟動原理

要想瞭解啟動原理,我們可以Debug模式跟著代碼一步步探究,我們從入口方法開始:

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
   return new SpringApplication(primarySources).run(args);
}

這裡是創建一個SpringApplication對象,並調用了run方法

2.1 創建SpringApplication對象

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //保存主配置類 
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //確定web應用類型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer;然後保存起來
    setInitializers((Collection) getSpringFactoriesInstances(
          ApplicationContextInitializer.class));
    //從類路徑下找到ETA-INF/spring.factories配置的所有ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //從多個配置類中找到有main方法的主配置類
    this.mainApplicationClass = deduceMainApplicationClass();
}

從這個方法中可以看出,這個

第一步:保存主配置類。

第二步:確定web應用類型。

第三步:setInitializers方法,這個方法走我們看帶入的參數是getSpringFactoriesInstances(ApplicationContextInitializer.class),我們再往下查看getSpringFactoriesInstances

再進入這個方法:

這裡就是從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然後再保存起來,放開斷點,我們可以看到這個時候獲取到的

第四步:從類路徑下找到ETA-INF/spring.factories配置的所有ApplicationListener,原理也基本類似,進入斷點

第五步:從多個配置類中找到有main方法的主配置類。這個執行完之後,SpringApplication就創建完成

2.2 run方法

先貼出代碼

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   //從類路徑下META-INF/spring.factories獲取SpringApplicationRunListeners
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //回調所有的獲取SpringApplicationRunListener.starting()方法
   listeners.starting();
   try {
      //封裝命令行參數
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
       //準備環境 
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);//創建環境完成後回調SpringApplicationRunListener.environmentPrepared();表示環境準備完成
      configureIgnoreBeanInfo(environment);
      //列印Banner圖
      Banner printedBanner = printBanner(environment);
      //創建ApplicationContext,決定創建web的ioc還是普通的ioc  
      context = createApplicationContext();
       //異常分析報告
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //準備上下文環境,將environment保存到ioc中
      //applyInitializers():回調之前保存的所有的ApplicationContextInitializer的initialize方法 
      //listeners.contextPrepared(context) 
      //prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded()
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       //刷新容器,ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat)
       //掃描,創建,載入所有組件的地方,(配置類,組件,自動配置)
      refreshContext(context);       
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      //所有的SpringApplicationRunListener回調started方法
      listeners.started(context);
      //從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調,
      //ApplicationRunner先回調,CommandLineRunner再回調
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
       //所有的SpringApplicationRunListener回調running方法
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   //整個SpringBoot應用啟動完成以後返回啟動的ioc容器
   return context;
}

前面的代碼不用分析,主要是準備對象,我們從 SpringApplicationRunListeners listeners = getRunListeners(args)開始分析,

第一步:是從類路徑下META-INF/spring.factories獲取SpringApplicationRunListeners,

這個方法跟前面分析的兩個獲取配置方法類似。

第二步:回調所有的獲取SpringApplicationRunListener.starting()方法。

第三步: 封裝命令行參數。

第四步:準備環境,調用prepareEnvironment方法。

第五步:列印Banner圖(就是啟動時的標識圖)。

第六步:創建ApplicationContext,決定創建web的ioc還是普通的ioc。

第七步:異常分析報告。

第八步:準備上下文環境,將environment保存到ioc中,這個方法需要仔細分析下,我們再進入這個方法

這裡面有一個applyInitializers方法,這裡是回調之前保存的所有的ApplicationContextInitializer的initialize方法

還有一個listeners.contextPrepared(context),這裡是回調所有的SpringApplicationRunListener的contextPrepared(),

最後listeners.contextLoaded(context) 是prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded()。

第九步:刷新容器,ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat),這個就是掃描,創建,載入所有組件的地方,(配置類,組件,自動配置)。

第十步:所有的SpringApplicationRunListener回調started方法。

第十一步:從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調,ApplicationRunner先回調,CommandLineRunner再回調。

第十二步:所有的SpringApplicationRunListener回調running方法。

第十三步:整個SpringBoot應用啟動完成以後返回啟動的ioc容器。

這就是run的全部過程,想要更詳細的瞭解還需自己去看源碼。

三、自定義starter

自定義starter(場景啟動器),我們要做的事情是兩個:確定依賴和編寫自動配置。我們重點要做的就是編寫自動配置,我們之前寫過一些自動配置,主要是註解配置的使用,主要的註解有:

  • @Configuration :指定這個類是一個配置類

  • @ConditionalOnXXX :在指定條件成立的情況下自動配置類生效

  • @AutoConfigureAfter:指定自動配置類的順序

  • @Bean:給容器中添加組件

  • @ConfigurationPropertie:結合相關xxxProperties類來綁定相關的配置

  • @EnableConfigurationProperties:讓xxxProperties生效加入到容器中

按照這些註解寫好自動配置類後,我們還需要進行自動配置的載入,載入方式是將需要啟動就載入的自動配置類,配置在META-INF/spring.factories,啟動器的大致原理是如此,而啟動器的實際設計是有一定模式的,就是啟動器模塊是一個空 JAR 文件,僅提供輔助性依賴管理,而自動配置模塊應該再重新設計一個,然後啟動器再去引用這個自動配置模塊。Springboot就是如此設計的:

另外還有一個命名規則:

官方命名空間

– 首碼:“spring-boot-starter-”

– 模式:spring-boot-starter-模塊名

– 舉例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

自定義命名空間

– 尾碼:“-spring-boot-starter”

– 模式:模塊-spring-boot-starter

– 舉例:mybatis-spring-boot-starter

3.1 創建自定義starter

第一步:因為我們需要創建兩個模塊,所以先新建一個空的項目,然後以模塊形式創建兩個模塊。

第二步:再創建兩個模塊,一個starter和一個自動配置模塊

具體的創建過程就不贅述了,就是最簡單的項目,去掉不需要的文件,創建完成結構如下:

第三步:我們先將自動配置模塊導入starter中,讓啟動模塊依賴自動配置模塊

啟動模塊的POM文件加入依賴

<dependencies>
    <!--引入自動配置模塊-->
    <dependency>
        <groupId>com.yuanqinnan-starter</groupId>
        <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

自動配置模塊的完整POM文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yuanqinnan-starter</groupId>
    <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--引入spring-boot-starter;所有starter的基本配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>

至此,兩個項目基本創建完成,現在我們實現簡單的配置。

第五步:對自動配置類進行自動配置代碼編寫

先編寫一個配置類,用於配置:

@ConfigurationProperties(prefix = "yuanqinnan.hello")
public class HelloProperties {
    //首碼
    private String prefix;
    //尾碼
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

再編寫一個服務

public class HelloService {

    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name) {
        return helloProperties.getPrefix() + "-" + name + helloProperties.getSuffix();
    }
}

然後再將這個服務註入組件:

@Configuration
@ConditionalOnWebApplication //web應用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;
    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }
}

這個時候我們的自動配置以及寫完,還差最後一步,因為SpringBoot讀取自動配置是在META-INF的spring.factories文件中,所以我們還要將我們的自動配置類寫入其中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.yuanqinnan.starter.HelloServiceAutoConfiguration

最後的結構如下:

至此,代碼以及編寫完成,這個時候我們將其裝入倉庫中,讓其他項目引用

3.2 使用自定義starter

創建一個web項目,然後在項目中引入依賴

<!--引入自定義starter-->
<dependency>
  <groupId>com.yuanqinnan.starter</groupId>
  <artifactId>yuanqinnan-springboot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

在application.properties 配置中加上配置:

yuanqinnan.hello.prefix=早安
yuanqinnan.hello.suffix=晚安

加入測試:

@Autowired
HelloService helloService;
​
@Test
public void contextLoads() {
    System.out.println(helloService.sayHello("世界"));
}

這樣自定義Starter和引用自定義都已完成,Springboot的核心知識已經總結完成,後面再進行Springboot的一些高級場景整合,如緩存、消息、檢索、分散式等。

 


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

-Advertisement-
Play Games
更多相關文章
  • Flask中使用cookie和session 設置cookie from flask import Flask,Response app = Flask(__name__) @app.route('/index') def index(): response = Response("設置cookie ...
  • 4.28日自我總結 一.關於python 二.PyCharm的安裝註意事項 1.激活碼 可以網上找 2.對於當中的Python的設置 對於python的路徑不能選擇系統預設,要手動輸入python.exe的路徑 3.字體設置以及快捷設置 點擊File→setting或者alt+ctrl+s 4.常用 ...
  • 準備工作 1.隨便準備一個項目工程,在本地用Pipenv創建一個虛擬環境並生成Pipfile和pipfile.lock文件,如下: 2.準備一臺伺服器,我這裡使用阿裡雲的ECS SSH連接上 Pycharm同步項目到伺服器 Tools Deployment Configuration 新增一個SFT ...
  • 1.pom.xml 2.UserConsumerDemoApplication.java 3.UserClient.java 4.UserFController.java ...
  • 基礎博弈論 博弈論,又稱對策論,是現代數學的一個分支,強調一個對策,看起來十分深奧,好像古代那些軍師的計謀。的確,博弈論是一門非常深奧的學科,在生活中也有不少運用,但作為信息學奧賽選手,我們沒有必要去專業的學習博弈論,只需要知道一些常見的博弈論以及它的結論和證明即可。 1、 巴什博弈 : 這是一個最 ...
  • 前言 Python虛擬環境是一個虛擬化,從電腦獨立開闢出來的環境。在這個虛擬環境中,我們可以pip安裝各個項目不同的依賴包,從全局中隔離出來,利於管理。 傳統的Python虛擬環境有virtualenv,使用pip freeze requirements.txt 導出依賴。現在又有了一個新神器 Pi ...
  • 1 路由 1.1app.url_map 查看所有路由 from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): """定義視圖函數""" print(app.url_map) return "he ...
  • 內容: 1、列表展示 2、輪播圖 3、其他 本次的內容也是在上一節的基礎上進行操作 我們就搞這個story模塊。 目錄: story.dart story主頁面 story_item.dart 構造列表頁面 裡面涉及到兩個公共庫 touch_callback.dart 觸摸回調 story_data ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...