---
title: 'SpringBoot3 學習紀錄'
disqus: hackmd
---
* 尚硅谷筆記:https://blog.csdn.net/qq_40734758/article/details/132910708
# 1. 概念
* **特性**
* 快速創建Spring應用
* 直接嵌入Tomcat、Jetty or Undertow(`無需要部署war包`)
* **重點**:提供可選的`starter`,簡化應用整合
* **場景啟動器**(`starter`)
* 導包一堆,控制好版本。
* 為每一種場景準備了一個依賴:web-starter、mybatis-starter、...
* 按需自動配置spring與第三方庫
* 如果這些場景我要使用(生效)。 這個場景的所有配置都會自動配置好
* **約定大於配置**:每個場景有很多默認配置
* 自定義:配置文件中修改幾項即可
* 提供**生產級特性**:
* 監控指標
* 健康檢查
* 外部化配置(`環境變數`、`指定讀取某個位置的application.properties`)
* 無代碼生成、**無xml**
# 2. 常用註解
## 2.1 組件註冊
* @Configuration:通用配置類
* @SpringBootConfiguration:標示為SpringBoot配置類(`與@Configuration相同`)
* @Bean、@Scope
* @Controller、@Service、@Repository、@Component
* @Import
* @ComponentScan
## 2.2 條件註解
* 如果條件成立,就**觸發指定行為**(`註冊Bean`)
```java=
public class AppConfig {
//有Student類,就觸發創建Student這個Bean
@ConditionalOnClass(name = "com.nicolas.springboot3_test1.entity.Student")
@Bean
public Student getStudent() {
return new Student();
}
}
```
* @**ConditionalOnXXX**:
* @ConditionalOnClass:如果類路徑中**存在**這個類,則觸發指定行為
* @ConditionalOnMissingClass:如果類路徑中**不存在**這個類,則觸發指定行為
* @ConditionalOnBean:如果容器中**存在**這個Bean,則觸發指定行為
* @ConditionalOnMissingBean:如果容器中不存在這個Bean,則觸發指定行為
## 2.3 屬性綁定
* @ConfigurationProperties:配置屬性類
* @EnableConfigurationProperties:**啟用**配置屬性類
## 2.4 自動配置原理
* **流程**:
1. **導入start-web**:導入web開發環境
* 場景啟動器匯入了相關場景的所有依賴:starter-json、starter-tomcat、spring-mvc
* 每個**場景啟動器**都引入一個s**pring-boot-starter**(`核心場景啟動器`)
* **核心場景啟動器**引入`spring-boot-autoconfigure`包
* `spring-boot-autoconfigure`裡面囊括了**SpringBoot所有支持場景下的所有配置**
* 只要這個包下的所有類別都能生效,那麼相當於**SpringBoot官方寫好的整合功能**
* **SpringBoot**默認掃描不到`spring-boot-autoconfigure`下寫好的所有**配致類**。(`這些配置類給我們做了整合操作`)
2. **主程序**:`@SpringBootApplication`
1. `@SpringBootApplication`由**三個註解**組成
* `@SpringBootConfiguration`:聲明是配置類
* `@ComponentScan`:掃描包
* `@EnableAutoConfiguration`
2. **SpringBoot**預設`只能掃描自己主程式所在的包及其下面的子包`,**掃描不到**`sprine-boot-autoconfigure`包中官方寫好的**配置類**
3. `@EnableAutoConfiguration`:SpringBoot**開啟自動配置**的核心
* **@Import(AutoConfigurationImportSelector.class)**:**批量**給容器導入組件
* ` AutoConfigurationImportSelector.selectImports`()方法
* **SpringBoot**啟動會預設載入**142個配置類**。
* 這**142個配置類**來自於 `/META-INF/spring/org.springframework.boot.autoconfigure.Autoconfiguration.imports`文件
* 項目啟動時,利用`@Import`批量導入組件機制把`sprine-boot-autoconfigure`包下的**142**個`xxxAutoConfiguration`類導入近來(**自動配置類**)
* 雖然導入142個
4. 按需生效
* 並不是142個配置類都會生效
* 每一個自動配置類,都會有**條件註解**`@ConditionalOnXXX`,只有條件生效,才會建立
5. `xxxAutoConfiguration`**自動配置類**
1. 給容器中使用`@Bean`放一堆組件
2. 每個**自動配置類**,都可能會有`@EnableConfigurationProperties`,用來把設定檔中配的**指定前綴的屬性值**封裝到`xxxProperties`類中
3. 以Tomcat為例:把服務器的所有配置都是以server開頭。配置都封裝到屬性類中
4. 給容器中放的所有組件的一些核心參數,都來自于`xxxProperties`。`xxxProperties`都是和**配置文件**綁定
* 只需要修改配置文件的值,核心組件的底層參數都能修改
7. **寫業務,無須關心各種整合**
# 3. 自定事件驅動
* **同一線程運行**:Spring Boot **預設事件**是`同步發送`的
* **訂閱者\監聽者**:**實現ApplicationListener<監聽事件>類** 或 **@EventListener在方法上**
* 使用:
* **@Bean**、**@EventListener**
* SpringApplication.addListeners(...) 或 SpringApplicationBuilder.listeners(...)。
* META-INF/spring.factories。
* @Order:指定訂閱者的處理順序(多個訂閱者的情況)
* **自定發布器**:可以給多個事件用來,發布事件
* 方式ㄧ:實現 **ApplicationEventPublisherAware**
* 方式二:注入 **ApplicationEventMulticaster**
* **自定事件**:繼承 **ApplicationEvent**,實現一個構造器
## 3.1 發布器
```java=
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
//發布器
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
//底層發送事件用的元件,SpringBoot 會透過 ApplicationEventPublisherAware 介面自動注入
private ApplicationEventPublisher applicationEventPublisher;
//所有事件都可以發送
public void sendEvent(ApplicationEvent event) {
//調用底層API發送事件
applicationEventPublisher.publishEvent(event);
}
//會被自動調用,把真正發送事件的底層給注入
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
```
## 3.2 事件
* **自定事件**:**繼承**`ApplicationEvent`,實現一個構造器
```java=
//登陸成功事件
public class LoginSuccessEvent extends ApplicationEvent {
public LoginSuccessEvent(User source) {
super(source);
}
}
```
* ApplicationEvent.**getSource**():獲取**傳入值**(`User類`)
## 3.3 訂閱者\監聽者
### 3.3.1 該事件發生的位置沒有事務
* 方式ㄧ:實現**ApplicationListener**,並註冊**LoginSuccessEvent事件**
```java=
//訂閱事件(LoginSuccessEvent)
//方式一:實現ApplicationListener,並註冊LoginSuccessEvent事件
@Component
public class AccountService implements ApplicationListener<LoginSuccessEvent> {
private final static Logger LOGGER= LoggerFactory.getLogger(AccountService.class);
@Transactional//如果下列方法體內有事務操作,需要添加
@Override
public void onApplicationEvent(LoginSuccessEvent event) {
LOGGER.info("ApplicationListener get Event:" + event);
//獲取事件中傳入的參數值
User source = (User) event.getSource();
System.out.println(source);
throw new RuntimeException("AAAA");//測試是否在同一條線程上
}
}
```
* 方式二:**@EventListener**
```java=
import com.nicolas.springboot3_test1.entity.Student;
import com.nicolas.springboot3_test1.entity.User;
import com.nicolas.springboot3_test1.event.LoginSuccessEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
@Configuration
public class AppConfig {
//訂閱事件:方式二:@EventListener
//訂閱LoginSuccessEvent
@Transactional//如果下列方法體內有事務操作,需要添加
@EventListener
public void getLoginSuccessEvent(LoginSuccessEvent event) {
LOGGER.info("@EventListener get Event: " + event);
//獲取事件中傳入的參數值
User source = (User) event.getSource();
System.out.println(source);
}
}
```
### 3.3.2 該事件發生的位置有事務
* @EventListener 改用 @TransactionalEventListener
## 3.4 使用
```java=
import com.nicolas.springboot3_test1.entity.User;
import com.nicolas.springboot3_test1.event.EventPublisher;
import com.nicolas.springboot3_test1.event.LoginSuccessEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController {
@Autowired
EventPublisher eventPublisher;
@GetMapping("/login")
public void login(){
//事件發送
//1. 準備事件
LoginSuccessEvent loginSuccessEvent = new LoginSuccessEvent(new User("A1", "PWD"));
//2. 發送事件
eventPublisher.sendEvent(loginSuccessEvent);
}
}
```
# X. SpringBoot生命週期
* **SpringApplicationRunListener**:**定義了多個回調方法**,可以在`SpringBoot生命週期`各個階段插入各自的邏輯
## X.1 SpringBoot生命週期監聽
* **圖**:

1. **引導**
1. **starting**:應用開始,SpringApplication的run方法一調用,只要有了Bootstrapcontext就開始執行
2. **environmentPrepared**:**環境準備完成**(`把啟動參數等綁定到環境變數中`),但**IOC容器**(`ApplicationContext`)還沒創建
3. **啟動**
1. **contextPrepared**:**IOC容器**(`ApplicationContext`)創建、準備完成,`主配置類未加載`
3. **contextLoaded**:**IOC容器**(`ApplicationContext`)**加載**(`加載主配置類`)完成,`IOC容器未刷新`
* **加載主配置類**:**知道目前有哪些Bean**
5. **started**:**IOC容器**(`ApplicationContext`)**刷新**(`創建Bean`)完成,`Runner未調用`
* Runner接口:ApplicationRunner、CommandLineRunner
7. **ready**:runner調用完成
5. **運行**:**context.isRunning()**
### X.1.1 SpringApplicationRunListener範例(生命週期監聽)
* **代碼**:
```java=
package com.nicolas.springboot3_test1.listener;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.time.Duration;
/**
* SpringApplicationRunListener 都調用一次
* 1. 引導:利用BootstrapContext 引導整個項目啟動
* starting:應用開始,SpringApplication的run方法一調用,只要有了Bootstrapcontext就執行
* environmentPrepared:環境準備好(把啟動參數等綁定到環境變數中),但ioc還沒創建
* 2. 啟動:
* contextPrepared:ioc容器建立、準備完成,但是sources(主配置類別)沒載入。(關閉引導啟動器(BootstrapContext))
* contextLoaded:ioc容器加載,但ioc容器並未刷新
* 加載 => 主配置類別加載
* 未刷新 => bean未創建
* ====IOC容器中並未創建bean=======
* started:ioc容器刷新(bean創建完成),未調用Runner接口的實現類
* Runner接口:ApplicationRunner、CommandLineRunner
* ready:調用Runner接口的實現類
* 3. 運行
* 以前步驟都正確執行,代表容器running。
*/
public class CoreListener implements SpringApplicationRunListener {
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
// listeners.starting(bootstrapContext, this.mainApplicationClass);
System.out.println("=====1. start====正在啟動");
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
// listeners.environmentPrepared(bootstrapContext, environment);
System.out.println("=====2. environmentPrepared====環境準備完成");
}
//ApplicationContext >> IOC容器:管理Bean的創建、加載、生命週期
//this.prepareContext中會調用contextPrepared、contextLoaded
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("=====3. contextPrepared====IOC容器準備完成");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("=====4. contextLoaded====IOC容器 加載完成");
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("=====5. started====啟動完成");
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("=====ready====準備就緒");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("=====failed====啟動失敗");
}
}
```
* 設定在`META-INF/spring.factories`:
* **org.springframework.boot.SpringApplicationRunListener**=`com.nicolas.springboot3_test1.listener.CoreListener`
## X.2 事件觸發機制
* **getSpringFactoriesInstances方法**:表示透過`SPI機制`來獲取對象,需要在`META-INF/spring.factories`中設定
* 讀取`META-INF/spring.factories`
* `spring.factories`的**SPI機制**採用此種**設定方式**:
* **全路徑類名**=該實現類**全路徑類名**
### X.2.1 各種回調監聽器
#### X.2.1.1 BootstrapRegistryInitializer
* **說明**:**感知引導初始化**
* **starting階段前會觸發**
* **SpringFactoriesLoader類**來獲取對象
* **設定位置**:`META-INF/spring.factories`
* **場景**:進行密鑰校對授權
* 創建**引導上下文**(`bootstrapContext`)觸發
#### X.2.1.2 ApplicationContextInitializer
* **說明**:**感知IOC容器初始化**
* **contextPrepared階段前會觸發**
* **設定位置**(`resource目錄下`):META-INF/spring.factories
#### X.2.1.3 **SpringApplicationRunListener**
* **說明**:**感知SpringBoot全階段生命周期** + SpringBoot各種階段都能夠自定義操作
* **設定位置**:META-INF/spring.factories
* **內容**:全路徑類名=該實現類全路徑類名
* `org.springframework.boot.ApplicationContextInitializer`=com.nicolas.springboot3_test1.listener.**CoreListener**
#### X.2.1.4 Runner 接口
* **ApplicationRunner**:**started階段後會觸發**,卡死就不會就緒
* @Bean
* **CommandLineRunner**:**started階段後會觸發**,卡死就不會就緒
* @Bean
#### X.2.1.5 ApplicationListener
* **說明**:**感知全階段**,基於**事件機制**(一旦到了哪個階段可以做別的事)
* 設定方式:
* `@Bean`或`@EventListener`:**事件驅動開發**
* `SpringApplication.addListeners()`或 `SpringApplicationBuilder.listeners()`
* META-INF/spring.factories
## X.3 ApplicationEvent 抽象類
* 於實現**事件驅動**的程式設計模型。它是 **Spring 事件機制的基礎** ,透過事件發布和監聽來實現鬆散耦合的元件之間的交互
* 事件驅動開發中的**事件**,需要`繼承ApplicationEvent`
### X.3.1 SpringBoot生命週期各個事件(9個)
* **事件順序和時機**:
1. **ApplicationStartingEvent**:應用啟動但未做任何事項,除非註冊過listeners and intializers
2. **ApplicationEnvironmentPreparedEvent**:Environment準備OK,但IOC並未
3. **ApplicationContextInitializedEvent**:IOC準備完成,ApplicationContextInitialized調用,但是任何bean未加載
4. **ApplicationPreparedEvent**:容器刷新之前,bean定義信息加載
5. **ContextRefreshedEvent**:**容器準備刷新**
6. **ApplicationStartedEvent**:容器刷新完成,runner未調用
7. `AvailabilityChangeEvent`:**對接K8s**,`LivenessState.CORRECT` 應用**存活**
* **存活探針**
8. **ApplicationReadyEvent**:任何Runner被調用
9. `AvailabilityChangeEvent`:**對接K8s**,`ReadinessState.ACCEPTING_TRAFFIC` 應用**就緒**,可以接受API請求
* **就緒探針**
* **ApplicationFailedEvent**:啟動錯誤事件
* **使用SpringBoot生命週期各個事件**:**監聽SpringBoot生命週期事件**
* **方式一**:使用 `@EventListener`
* **方式二**:實作 `ApplicationListener\<SpringBoot生命週期各個事件>` 接
## X.4 SpringApplicationRunListener各SpringBoot生命週期回調方法 + SpringBoot生命週期各個事件 + 回調監聽器

# 4. 自動配置原理
## 4.1 入門
* 應用關注的三大核心:場景、配置、組件(`Bean`)
### 4.1.1 自動配置流程

### 4.1.2 SPI機制
* **自動配置流程**採用**SPI機制**
* **作用**:應用程序中**動態的發現**和**加載組件**
* SpringBoot是使用**SPI機制**的思想
* **思想**:定義一個**介面**或**抽象類**,然後透過在 classpath 中定義實作該介面的類別來實現對元件的動態發現和載入。
* Java**默認的SPI機制**是搜尋`META-INF/services`下的文件
* **SpringBoot**建議參考:
* `spring-boot-autoconfigure`包下的`META-INF/spring`
### 4.1.3 功能開關
* 自動配置:全部配置好,不需要管理
* 項目一啟動,SPI中指定的所有就都加載
* **@EnableXXX**:手動控制哪些功能的開啟,手動匯入
* 開啟 xxx 功能。
* 利用 **@Import** 把此功能要用的組件導入進去。
* 常見功能開關
* @EnableScheduling:開啟排成器
* @EnableAsync:開啟異步機制
## 4.2 進階
### 4.2.1 @SpringBootApplication
* **@SpringBootConfiguration**:是 **@Configuration**,容器中的元件,`標示為配置類`
* Spring IOC 啟動就會載入建立這個類別物件。
* **@EnableAutoConfiguration**:**開起自動配置功能**
* **@AutoConfigurationPackage**:**掃描主程式中的包及其子包**
* 利用 **@Import({AutoConfigurationImportSelector.class})** 想要給容器導入組件
* **AutoConfigurationImportSelector** 把**主程式所在的pacakge**的所有組件導入進來(`解釋 為什麼 SpringBoot 預設只掃描主程式所在的套件及其子包`)
* **@Import(AutoConfigurationImportSelector.class)**:**載入所有自動配置類**,載入 starter 導入的組件。
* **AutoConfigurationImportSelector**.**getCandidateConfigurations()** 方法中,掃描要載入的組件(`SPI機制`)
* **掃描 SPI 檔案**:`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
* **@ComponentScan**:組件掃描
* **源碼**:
```java=
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
```
* 排除組件,排除前面已經掃描進來的**配置類**、和**自動配置類**
### 4.2.2 生命週期啟動載入機制

# 5. 自定義 start
* **場景**:抽取聊天機器人場景,它可以打招呼。
* **效果**:任何專案匯入此 starter 都具有打招呼功能,且問候語中的人名需要可以在設定檔中修改。
* **步驟**:
1. 建立自訂 starter 項目,引入 spring-boot-starter 基礎依賴。
2. 編寫模組功能,引入模組所有需要的依賴。
3. 編寫 **xxxAutoConfiguration** 自動配置類,幫其他項目導入這個模組所需的所有元件。
5. 使用**自定註解**(`@EnableXxx`)或編寫**SPI設定檔** `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 指定啟動需要載入的自動設定。
```java=
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(SourceAutoConfiguration.class)
public @interface EnableSource {
}
```
7. 其他項目引進即可使用
* 項目如果採用**自定註解**方式,該項目需要添加該`@EnableXxx`
* 非SpringBoot官方的**starter**,導入後發現`沒有上述的SPI 檔案`,**為SpringBoot2**(`該依賴無法被自動Import`)
* 可再確認是否有無`@EnableXxx`
## 5.1 依賴
```xml=
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- properties配置處理器,提示自定義properties-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
```
## 5.2 代碼
* XxxProperties類
```java=
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "roboot")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RobootProperties {
private String name;
private Integer age;
private String email;
}
```
* Server類(`業務邏輯`)
```java=
import com.nicolas.roboot.properties.RobootProperties;
import org.springframework.stereotype.Service;
@Service
public class RobootService {
private final RobootProperties robootProperties;
public RobootService(RobootProperties robootProperties) {
this.robootProperties = robootProperties;
}
public String sayHello() {
return String.format("name: %s, age: %d, email: %s", robootProperties.getName(), robootProperties.getAge(), robootProperties.getEmail());
}
}
```
* XxxAutoConfiguration類
```java=
import com.nicolas.roboot.properties.RobootProperties;
import com.nicolas.roboot.service.RobootService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//導入Roboot功能需要用的組件
@EnableConfigurationProperties(RobootProperties.class)
@ConditionalOnClass(RobootService.class)
@Configuration
public class RobootAutoConfiguration {
private RobootProperties robootProperties;
// 自動注入配置類
public RobootAutoConfiguration(RobootProperties robootProperties) {
this.robootProperties = robootProperties;
}
@Bean
@ConditionalOnBean(RobootProperties.class)
public RobootService getRobootService() {
return new RobootService(robootProperties);
}
}
```
## 5.3 功能生效
### 5.3.1 方式ㄧ:@EnableXxx機制
* @EnableXxx
```java=
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RobootAutoConfiguration.class)
public @interface EnableRoboot {
}
```
* 將**RobootAutoConfiguratio類**給導入`IOC容器`
* **使用**:
1. 依賴該pacakage
2. 將 **@EnableRoboot** 註解,貼在該項目的啟動類上
### 5.3.2 方式二:完全自動配置
* **依賴 SpringBoot 的 SPI 機制**
* **步驟**:
* **創建檔案**(`resources目錄下`):`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
* 編寫**自動配置類**的**全路徑類名**(`與spring.factories的設定方式不同`)
```properties=
com.nicolas.roboot.RobootAutoConfiguration
```
* **使用**:
1. 依賴該pacakage後,啟動時,就會**自動加載**(`掃描每個依賴package下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,如果有該檔案的話`)
# 6. Swagger文檔
* **OpenAPI 3 架構**

* springdoc-openapi-starter-webmvc-**ui**:**UI層**
* 導入`swagger-ui`,**可視化的展現**接口文檔
* springdoc-openapi-starter-webmvc-**api**:**API層**
* 適配**OpenAPI**規範
## 6.1 整合
* **依賴**
```xml=
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</dependency>
```
* **可選配置**
```properties=
# /api-docs endpoint custom path # /v3/api-docs
springdoc.api-docs.path=/api-docs
#swagger相關配置在springdoc.swagger-ui
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.show-actuator=true
```
## 6.2 使用
* 該版本默認**自動導入Swagger**,不需要添加`註解`、`配置類`
* **SPI機制**(`org.springframework.boot.autoconfigure.AutoConfiguration.imports`)
### 6.2.1 常用註解
| 註解 | 標示位置 | 作用|
| -------- | -------- | -------- |
| @Tag | controller類 | 標示Controller的作用 |
| @Parameter | 參數 | 標示參數作用 |
| @Parameters | 參數 | 參數多重說明 |
| @Schema | model層的JavaBean | 描述模型作用及每個屬性 |
| @Operation | 方法 | 描述方法作用 |
| @ApiResponse | 方法 | 描述響應狀態等 |
| @Hidden | 方法\類型\屬性 | 隱藏 |
### 6.2.2 Docket設置
* **GroupOpenApi**類:**分組設置**,區分哪些路徑到哪一個文檔
* **舊版本**:**Docket類**
* **整個文檔說明設定**:**兩種方式**
* **註解**:`@OpenAPIDefinition`
* **類**:`OpenAPI類`
* **配置**:
```java=
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration("SwaggerConfig")
@OpenAPIDefinition(info = @Info(title = "EunoAI APIi", version = "v1.0.0"),
security = {@SecurityRequirement(name = "api_key")})
@SecurityScheme(type = SecuritySchemeType.HTTP, name = "api_key", scheme = "bearer")
public class SwaggerConfig {
@Bean
public GroupedOpenApi payApi() {
return GroupedOpenApi.builder()
.group("Pay Group")
.pathsToMatch("/pay/**")
.build();
}
}
```
* **@SecurityScheme**:設定Security
* `scheme = "bearer"`:token 前面才會帶Bearer
* **@Info**:之前的**ApiInfo**類
* 設定整個頁面的信息
* **配置API不需要 Security**:該方法添加 **@SecurityRequirements()**
### 6.2.3 application.properties配置
* 使用 Spring 框架的支援來**處理轉送的請求頭**(`使用Nginx`)
```properties=
server.forward-headers-strategy=framework
```
* **控制是否啟用Swagger**,默認啟用
* 源碼中**SwaggerConfig**的設定
* `@ConditionalOnProperty(name = "springdoc.swagger-ui.enabled", matchIfMissing = true)`
* **屬性**:`springdoc.swagger-ui.enabled`(`唯一控制啟動方式`)
# 7. 遠程調用
* SpringBoot整合提供了很多方式進行遠端調用
* 輕量級客戶端方式
* RestTemplate:普通開發
* WebClient:響應式程式設計開發
* HttpInterface:聲明式編程+響應式
* SpringCloud分散式解決方案方式
* Spring Cloud OpenFeign
* 第三方框架
* Dubbo
* gRPC
* API/SDK的區別
* API:接口
* 遠程提供功能
* SDK:工具包
* 導入jar包,直接調用功能
## 7.1 WebClient
* 非阻塞式,響應式的客戶端
* **依賴**:spring-boot-starter-webflux(`響應式編程`)
## 7.2 HttpInterface
1. **依賴**:spring-boot-starter-webflux(`響應式編程`)
2. 定義接口:
```java=
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import reactor.core.publisher.Mono;
public interface WeatherInterface {
//發送get請求,設定URL全路徑
@GetExchange(url = "http://localhost:8080/area",accept = MediaType.APPLICATION_JSON_VALUE)//接受json數據
Mono<String> getWeather(@RequestParam("city") String city,//參數
@RequestHeader("Authorization") String token//請求頭
);
//發送get請求,設定URL路徑,IP/域名的部分 => 設定在WebClient中的baseUrl(http://localhost:8080/)
@GetExchange("/test/task")
Task getTask(@RequestBody Task task);
}
```
4. 定義代理:默認為響應模式
```java=
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class WeatherConfig {
@Bean
public HttpServiceProxyFactory getHttpServiceProxyFactory(){
//1.創建客戶端
WebClient webClient = WebClient.builder()
.defaultHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
//響應資料量太大有可能會超出Buffersize,所以這裡設定的大一點
.codecs(clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(256 * 1024 * 1024))
//錯誤處理
.defaultStatusHandler(
HttpStatusCode::isError,
clientResponse -> clientResponse.bodyToMono(Map.class).map(e -> new RuntimeException((String) e.get("error")))
)
.build();
//2. 創建工廠
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder()
.exchangeAdapter(WebClientAdapter.create(webClient))
.build();
return factory;
}
@Bean
public WeatherInterface getWeatherInterface(HttpServiceProxyFactory factory) {
//3. 獲取代理對象
return factory.createClient(WeatherInterface.class);
}
}
```
6. 使用
```java=
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class WeatherService {
private final WeatherInterface weatherInterface;
public WeatherService(WeatherInterface weatherInterface) {
this.weatherInterface = weatherInterface;
}
public Mono<String> getWeather(String city) {
return weatherInterface.getWeather(city, "header");
}
}
```
# 8. 可觀測性
* SpringBoot Actuator
```xml=
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.3.0</version>
</dependency>
```
* 對線上應用,進行觀測、監控、預警
* 健康狀況
* 組件狀態
* 存活狀態
* 運行指標
* CPU
* 內存
* GC
* 吞吐量
* 響應成功機率
* 鏈路追蹤
## 8.1 實戰
* 暴露指標
```properties=
# 通過web方式暴露所有監控端點信息
management.endpoints.web.exposure.include=*
```
* 訪問數據:訪問`http://localhost:8080/actuator` 展示所有可用的監控端點
## 8.2 Endpoint
### 8.2.1 常用端點
| ID | 描述 |
| -------- | -------- |
| auditevents | 揭露當前應用程式的審核事件資訊。需要一個`AuditEventRepository`組件|
| beans |顯示應用程式中所有Spring Bean的完整清單。 |
| caches | 暴露可用的緩存 |
| conditions | 顯示自動配置的所有條件信息,包括匹配或不匹配的原因。 |
| configprops | 顯示所有`@ConfigurationProperties` |
| env | 暴霱Sprinq的屬性`ConfigurableEnvironment` |
| flyway | 顯示已套用的所有**Flyway**資料庫遷移。<br>需要一個或多個`Flyway`組件。 |
| health | 顯示應用程式運行狀況資訊 |
| httptrace | 顯示HTTP追蹤訊息(預設情況下,最近100個HTTP請求-回應)。<br>需要一個HttpTraceRepository元件。 |
| info | 顯示應用程式信息 |
| integrationgraph | 顯示Spring **integrationgraph**。需要依赖`spring-integration-core` |
| loggers | 顯示和修改應用程式中日誌的配置 |
| liquibase | 顯示已套用的所有**Liquibase**資料庫遷移。需要一個或多個Liquibase組件。|
| metrics | 顯示目前應用程式的 **「指標」** 資訊 |
| mappings | 顯示所有`@RequestMapping`路徑清單 |
| scheduledtasks | 顯示應用程式中的**排程任務** |
| sessions | 允許從**Spring Session**支援的會話儲存中檢素和刪除使用者會話。需要使用**Spring Session**的基於**Servlet**的**Web**應用程式。 |
| shutdown | 使應用程式正常關閉。預設禁用 |
| startup | 顯示由`ApplicationStartup`收集的啟動步驟資料。需要使用`SpringApplication`進行設定`BufferingApplicationStartup` |
| threaddump | 執行線程轉儲。 |
| headdump | 返回`hprof`堆內存文檔 |
| jolokia | 透過**HTTP**暴露JMX bean(需入**Jookia**,不適用於**WebFlux**)。需要引入依賴`jolokia-core ` |
| logfile | 返回日誌檔案的內容(如果已設定`1ogging.file.name`或`logging.file.path`屬性)。支援使用**HTTP** `Range`標頭來檢索部分日誌文件的內容 |
* **重要Endpoint**:`threaddump`、`headdump`、`metrics`
### 8.2.2 定製端點
* **健康監控**(`Health`):返回組件存活、死亡
* **指標監控**(`Metrics`):次數(`API調用`)、速率
#### 8.2.2.1 健康監控
* 設定暴露所有健康信息
```properties=
# 顯示健康端點的詳細信息
management.endpoint.health.show-details=always
```
* 被監控類:
```java=
import org.springframework.stereotype.Component;
@Component
public class MyHealthComponent {
public Integer check(){
//業務代碼判斷這個組件是否該是存活狀態
return 1;
}
}
```
* 監控類:繼承**AbstractHealthIndicator**抽象類
```java=
import com.nicolas.springboot3_test1.actuator.component.MyHealthComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;
//實現HealthIndicator接口,定製組件健康狀態返回
@Component
public class MyHealthIndicator extends AbstractHealthIndicator {
@Autowired
private MyHealthComponent myHealthComponent;
//健康檢查
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//自定義檢查方法
Integer check = myHealthComponent.check();
if (check == 1) {
//存活
builder.up()
//配置健康信息
.withDetail("code","1000")
.withDetail("msg","health")
.build();
} else {
//下線
builder.down()
.withDetail("code","1001")
.withDetail("msg","dead").build();
}
}
}
```
* **信息返回**:`http://localhost:8080/actuator/health`
```josn=
{
"status": "UP",
"components": {
"diskSpace": {
"status": "UP",
"details": {
"total": 1995218165760,
"free": 1888140931072,
"threshold": 10485760,
"path": "/Users/nicolas/SpringBoot3/springboot3_test1/.",
"exists": true
}
},
"my": {
"status": "UP",
"details": {
"code": "1000",
"msg": "health"
}
},
"ping": {
"status": "UP"
}
}
}
```
#### 8.2.2.2 指標監控
* **目標**:**計算方法調用次數**
* 使用**MeterRegistry類**(`注入到被監控類`)來進行各種指標統計
* **被監控類**:
```java=
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;
@Component
public class MyMetricsComponent {
//Spring規定,如果有參構造器只有一個參數,該參數會從IOC容器中獲取後,注入到MyMetricsComponent中
Counter counter = null;
public MyMetricsComponent(MeterRegistry meterRegistry) {
//counter():方法計數器
counter = meterRegistry.counter("mymetrics.sayhello");
}
public void sayHello() {
System.out.println("hello");
counter.increment();
}
}
```
* **指標名**:`mymetrics.sayhello`
* **使用**:`http://localhost:8080/actuator/metrics/{指標名}`
* `http://localhost:8080/actuator/metrics/mymetrics.sayhello`
* **返回**:
```json=
{
"name": "mymetrics.sayhello",
"measurements": [
{
"statistic": "COUNT",
"value": 2.0
}
],
"availableTags": []
}
```
###### tags: `SpringBoot` `Java17`