Spring Boot 知識清單(一)SpringApplication

架構技術專欄發表於2020-09-24

愛生活,愛編碼,微信搜一搜【架構技術專欄】關注這個喜歡分享的地方。本文 架構技術專欄 已收錄,有各種JVM、多執行緒、原始碼視訊、資料以及技術文章等你來拿。

一、概述

目前Spring Boot已經發展到2.3.4.RELEASE ,對於它的好處網上也是鋪天蓋地的,這裡就不再重複了。直接說重點,從Spring Boot1.x一步步跟著迭代升級到現在的2.3.4也是遇到了很多的坑,瞭解其新版本的特性是非常重要的,可以幫助我們避免很多不必要的麻煩。

因為我也一直在搞基於Spring Boot技術棧的元件開發工作,最近準備針對基礎元件進行部分重構,所以順便把當前版本的特性從頭在順一遍,就當是回顧總結了,這個回顧只介紹目前版本的一些特性,不對特性展開來敘述,如果有興趣可以@我,我也會根據某一塊來進行詳細的分析。喜歡的朋友可以跟著看一看,希望對你有所幫助。

二、從頭開始Application

首先我們先得來初始化各專案用於測試,大家可以使用官方的https://start.spring.io/ 生產,或者使用IDEA的外掛Spring Boot進行專案初始化,文末我也會放下我測試demo的地址。

1、應用啟動失敗(Startup Failure)

如果應用啟動失敗,Spring Boot會幫我們把大概為什麼會啟動失敗的資訊列印在日誌中,如下面我用6080埠第二次啟動應用就會提示我如下

***************************
APPLICATION FAILED TO START
***************************

Description:

Embedded servlet container failed to start. Port 6080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

有了這種友好的提示真是幸福感爆棚啊,而且Spring Boot 還給我們提供了更多的擴充套件介面FailureAnalyzer,並提供了響應得抽象類AbstractFailureAnalyzer。

如果我們不滿足他預設的啟動異常資訊,就可以通過FailureAnalyzer 來進行一些定製化開發(比如在異常發生的時候列印堆疊等)。

FailureAnalyzer的擴充套件使用了SPI的方式,所以在我們使用的時候需要在應用內建立META-INF/spring.factories,來宣告下我們的實現,下面上個小demo。

/** 首先建立我們自己的類,並且可以根據自己的需要來進行異常攔截,這裡我攔截的就是埠占用異常PortInUseException
 * @ClassName LearningFailureAnalyzer
 * @Author QIANGLU
 * @Date 2020/9/23 9:10 下午
 * @Version 1.0
 */
public class LearningFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {

    private static final Logger LOGGER = LoggerFactory.getLogger(LearningFailureAnalyzer.class);

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
        LOGGER.error("我出異常了,哇卡卡卡卡卡卡");
        return new FailureAnalysis("埠:" +cause.getPort()+"被佔用",cause.getMessage(),rootFailure);
    }
}


//第二步就是建立個META-INF/spring.factories 了,如下
  
org.springframework.boot.diagnostics.FailureAnalyzer=\
  com.qianglu.chapter1.failure.LearningFailureAnalyzer
  
 
//第三布啟動兩次我們的應用,就會發現,列印的資訊是我們需要的了
 ***************************
APPLICATION FAILED TO START
***************************

Description:

埠:6080被佔用

Action:

Port 6080 is already in use
  

這東西可用場景其實很多很多,大家想一想有沒有點啟發

2、延遲初始化(Lazy Initialization)

在Spring Boot剛出的時候,因為啟動載入慢還被人吐槽過,這不,現在懶載入來了,允許你的應用開啟懶載入,你的beans 不需要在專案啟動的時候被建立了,啥時候用啥時候在建立。

這樣就能節省你很多啟動時間,但有利就有弊,懶載入這玩意在web應用中會導致你很多web相關的bean也被延遲載入,知道有請求進來才會被初始化,所以在使用的時候一定要注意,否則就會有叫你很懵逼的異常了。

並且官方也說了,你都延遲初始化了,那有些問題可能也會延遲被發現。比如我們以前某些配置配錯了,經常會在啟動的時候就報XXXbean不能被找到之類的。嘿嘿,現在可就不了,一樣的啟動成功,只有在你用的時候給你掉鏈子,就問你怕不怕吧。

還有就是官方提示延遲初始化的,會導致初期jvm 記憶體表現比較小,但要注意配置足夠的記憶體給未來物件建立使用(我覺得一般應用這都不是問題,不需要過多關注)。

下面我們就來看看兩種配置方式:

  • 使用SpringApplication呼叫setLazyInitialization 方法設定
  • 使用配置spring.main.lazy-initialization=true

如果你設定了延遲初始化,又有某些特殊的類想初始化,那可以配置@Lazy(false) 關閉其懶載入。

3、配置Banner

這玩意說實話我一直不知道有啥用,以前我們都是配置個大佛保平安,娛樂性大於實際吧,當然也許有沒GET到的點。

配置方式也很簡單,就是在你的classpath下放個banner.txt,通過spring.banner.location 配置來指定下檔案位置。當然還有很多屬性,什麼編碼、gif、version之類的我其實懶得說了,沒啥興趣。

4、配置你的SpringApplication

我們們一般啟動類都是直接呼叫SpringApplication.run就行了。但如果你覺得太簡單沒啥意思,那其實SpringApplication.run 裡面有很多有意思的屬性你可以去看看,比如我關閉banner

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.run(args);
}

當然還有很多配置屬性,你也可以使用application.yml來配置SpringApplication 的屬性。

5、流式構建API(Fluent Builder API)

官方提供了SpringApplicationBuilder 類來幫大家使用流式構建的方式來建立多級的ApplicationContext。SpringApplicationBuilder可以幫助我們構建一種層級關係,如下這種方式等於SpringApplication.run

new SpringApplicationBuilder()
        .sources(Parent.class)
        .child(Application.class)
        .bannerMode(Banner.Mode.OFF)
        .run(args);

6、應用可用性(Application Availability)

這裡說的其實就是k8s的Liveness 和 Readiness ,他們已經成為了Spring Boot的核心。

簡單說下,Liveness 和 Readiness 在k8s中代表了應用程式狀態的各個方面。

Liveness 狀態來檢視內部情況可以理解為health check,如果Liveness失敗就就意味著應用處於故障狀態並且目前無法恢復,這種情況就重啟吧。

Readiness 狀態用來告訴應用是否已經準備好接受客戶端請求,如果Readiness未就緒那麼k8s就不能路由流量過來。

我們可以用程式碼來監聽Readiness狀態,並進行我們需要的處理

@Component
public class ReadinessStateExporter {

    @EventListener
    public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
        switch (event.getState()) {
        case ACCEPTING_TRAFFIC:
            // xxxxx
        break;
        case REFUSING_TRAFFIC:
            // xxxxxx
        break;
        }
    }

}

我們也能在應用出現故障不能被恢復的時候改變此狀態來進行動態的降級和隔離,這個真是太爽了,有機會建議大家試一試

@Component
public class LocalCacheVerifier {

    private final ApplicationEventPublisher eventPublisher;

    public LocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void checkLocalCache() {
        try {
            //...
        }
        catch (CacheCompletelyBrokenException ex) {
            AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
        }
    }

}

7、事件和監聽(Application Events and Listeners)

Spring Boot的事件通知機制,簡直是解耦神器,包括Spring Cloud的用的分散式遠端通知機制其實核心也是這個,這是增加了一層中介軟體來進行訊息的傳遞。

有些事件因為實在ApplicationContext建立前就觸發了,所以很多時候不能使用@Bean來宣告這些事件。最好使用SpringApplication.addListeners(…)SpringApplicationBuilder.listeners(…) 來註冊監聽器。

但如果你真是把握不了這些載入時機的話,那有個萬能的辦法就是配置SPI擴充套件,直接在META-INF/spring.factories 配置,加上你得listener就行了,如:org.springframework.context.ApplicationListener=com.example.project.MyListener

官方介紹了寫在啟動時候會傳送的事件順序:

1、ApplicationStartingEvent 在執行開始的時候傳送事件

2、ApplicationEnvironmentPreparedEvent 當Environment在上下文中被使用的時候傳送事件

3、ApplicationContextInitializedEvent 在所有的bean定義前,ApplicationContext準備好並且ApplicationContextInitializers已經被呼叫的時候傳送事件

4、ApplicationPreparedEvent 重新整理配置前、bean的定義載入之後傳送事件

5、ApplicationStartedEvent 重新整理上下文後,在執行CommandLineRunner的實現之前

6、AvailabilityChangeEvent 傳送LivenessState.CORRECT 表面應用是活躍狀態

7、ApplicationReadyEvent 在執行CommandLineRunner介面之後傳送

8、AvailabilityChangeEvent 傳送ReadinessState.ACCEPTING_TRAFFIC 後代表應用可以接入流量

9、ApplicationFailedEvent 傳送應用啟動失敗事件

上面的只是SpringApplicationEvent 的事件,一般我們們也不需要對這些進行操作,帶你得知道它的存在,以免出了問題都不知道怎麼找,其實人家Spring Boot已經都發給你了。

8、Web屬性

一般使用SpringApplication就會為我們正確的建立ApplicationContext型別,用於確定WebApplicationType 也就是應用型別的方式其實很簡單:

  • 如果存在Spring MVC 就使用AnnotationConfigServletWebServerApplicationContext
  • 如果Spring MVC不存 在,但是Spring WebFlux存在,就使用AnnotationConfigReactiveWebServerApplicationContext
  • 都沒有的話就用AnnotationConfigApplicationContext
  • 如果你既用了Spring MVC 又用了Spring WebFlux WebClient,Spring MVC 這一套是預設使用的,除非你設定SpringApplication.setWebApplicationType(WebApplicationType)來強制改變。

9、訪問應用引數(Accessing Application Arguments)

如果你想訪問SpringApplication.run(…) 的引數,你其實可以注入一個org.springframework.boot.ApplicationArguments 物件,ApplicationArguments這個介面提供對原始String[] 引數以及已解析的選項和非選項引數的訪問,上demo:

import org.springframework.boot.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;

@Component
public class MyBean {

    @Autowired
    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
    }

}

Spring Boot 中的環境變數也註冊了一個CommandLinePropertySource,我們可以使用@Value來獲取某個環境變數。

10、使用ApplicationRunner or CommandLineRunner

這倆貨也是我們經常會用到的東西,如果你需要在專案啟動時載入一些東西,那它倆簡直就是神器了,這倆介面都提供了一個run方法,這個方法會在SpringApplication.run(…) 執行完成前被呼叫。這倆介面適合哪種在應用接收請求前來處理一些東西。

舉個使用例子

import org.springframework.boot.*;
import org.springframework.stereotype.*;

@Component
public class MyBean implements CommandLineRunner {

    public void run(String... args) {
        // Do something...
    }

}

如果我們們定義了多個CommandLineRunner或ApplicationRunner實現,有的時候又需要有個先後順序來執行,那就可以用org.springframework.core.annotation.Order 這個註解來定義下。

11、應用退出( Application Exit)

每個SpringApplication 都會像JVM註冊一個關閉鉤子(shutdown hook ),來確保能夠正常的退出。保證@PreDestroy 註解和DisposableBean 介面這些回撥都被執行。

另外,如果你想在使用SpringApplication.exit() 時返回一些特殊的退出程式碼,可以實現org.springframework.boot.ExitCodeGenerator介面,傳遞給System.exit() 進行返回。如:

@SpringBootApplication
public class ExitCodeApplication {

    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 42;
    }

    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(ExitCodeApplication.class, args)));
    }

}

ExitCodeGenerator介面可以通過異常實現。遇到此類異常時,Spring Boot返回實現的getExitCode() 方法提供的退出程式碼

12、管理員功能(Admin Features)

我們可以使用spring.application.admin.enabled 屬性來開啟管理員功能。開了的話就會把你自己的SpringApplicationAdminMXBean 全部暴露給MBeanServer咯,當然你也可以用這種特性來遠端操作你應用。但你要想明白其中的安全性問題,沒啥必要的話還是不要亂搞。

三、總結

好了,今天太晚了,就先寫到這,其實這些內容官方寫的都是明明白白的,但自己擼一遍還是很爽,很舒服。在擼的過程中其實你會結合自己實際所用的知識點進行一些思考,新的特性也會給你帶來很多未來基礎設計上的啟發。希望大家有時間精力的話多看看,最起碼在需要的時候我知道有這麼東西,就有了方向。

愛生活,愛編碼,微信搜一搜【架構技術專欄】關注這個喜歡分享的地方。本文 架構技術專欄 已收錄,有各種JVM、多執行緒、原始碼視訊、資料以及技術文章等你來拿。

相關文章