Spring Boot3 + JDK21 的遷移超詳細(xì)步驟(最新推薦)
從 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生產(chǎn)環(huán)境遷移實(shí)戰(zhàn)
升級背景
在私有化部署過程中,客戶使用安全掃描工具檢測到大量安全漏洞,主要集中在:
- 框架版本過低:Spring Boot 2.1.6.RELEASE(發(fā)布于 2019 年)
- JDK 版本過舊:JDK 8(缺乏最新安全補(bǔ)?。?/li>
- 第三方依賴:多個(gè)依賴存在已知 CVE 漏洞
基于安全合規(guī)和長期維護(hù)的考慮,決定進(jìn)行大版本升級。
- 當(dāng)前版本:Spring Boot 2.1.6.RELEASE + JDK 8
- 目標(biāo)版本:Spring Boot 3.5.4 + JDK 21 LTS
升級目標(biāo)與核心變化
主要變化
| 類別 | 變化內(nèi)容 | 遷移方式 |
|---|---|---|
| 命名空間 | javax.* → jakarta.* | 自動(dòng)化遷移 |
| JDK 版本 | Java 8 → Java 21 LTS | 自動(dòng)化遷移 + 手動(dòng)調(diào)整 |
| 第三方依賴 | 大量依賴需要升級 | 手動(dòng)處理 |
| API 文檔 | Swagger 2.x → SpringDoc OpenAPI 3.x | 配置調(diào)整 |
| 安全配置 | WebSecurityConfigurerAdapter 廢棄 | 重寫配置類 |
為什么選擇自動(dòng)化遷移?
前兩項(xiàng)(命名空間和 JDK 版本)涉及的代碼改動(dòng)量極大,手動(dòng)修改容易出錯(cuò)且效率低下。
OpenRewrite 作為業(yè)界成熟的自動(dòng)化重構(gòu)工具,可以完成大部分繁瑣工作。
完整升級步驟
第一階段:準(zhǔn)備工作(JDK 8 環(huán)境)
代碼分支管理
# 確保主分支代碼為最新 git checkout dev git pull origin dev # 創(chuàng)建升級專用分支 git checkout -b upgrade/springboot3-jdk21
引入 OpenRewrite Maven 插件
什么是 OpenRewrite?
OpenRewrite 是一個(gè)自動(dòng)化代碼重構(gòu)和遷移工具,專為 Java 生態(tài)系統(tǒng)設(shè)計(jì)。
核心優(yōu)勢:
- 精確安全:在 AST(抽象語法樹)層面操作,不會破壞代碼結(jié)構(gòu)
- 批量處理:一次性處理整個(gè)代碼庫
- 可預(yù)覽:使用
rewrite:dryRun查看變更預(yù)覽 - 可定制:支持聲明式(YAML)或編程式自定義規(guī)則
工作原理:
OpenRewrite 通過解析源代碼生成無損語法樹(LST),在 AST 層面進(jìn)行精確轉(zhuǎn)換,完整保留:
- 原始格式和縮進(jìn)
- 所有注釋
- 代碼風(fēng)格
配置方式:
在 pom.xml 的 <plugins> 節(jié)點(diǎn)下添加:
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>6.15.0</version>
<configuration>
<exportDatatables>true</exportDatatables>
<activeRecipes>
<!-- 升級到 Java 21 -->
<recipe>org.openrewrite.java.migrate.UpgradeToJava21</recipe>
<!-- JUnit 4 to 5 -->
<recipe>org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration</recipe>
<!-- Spring Boot 3.4(插件暫不支持 3.5,升級后手動(dòng)改) -->
<recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-migrate-java</artifactId>
<version>3.14.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-spring</artifactId>
<version>6.11.1</version>
</dependency>
</dependencies>
</plugin>配方(Recipe)說明:
UpgradeSpringBoot_3_4 :升級至 Spring Boot 3.4.x(插件暫不支持 3.5,升級后手動(dòng)修改版本號即可)UpgradeToJava21 :升級至 JDK 21(Spring Boot 配方僅升級到 JDK 17,需額外添加此配方)SpringBoot2JUnit4to5Migration :升級測試框架,避免自動(dòng)化測試報(bào)錯(cuò)
提示:你也可以編寫自定義配方來處理項(xiàng)目特定的遷移需求。
執(zhí)行自動(dòng)化遷移
mvn rewrite:run
或者在 IDEA 中通過 Maven 面板執(zhí)行:

執(zhí)行時(shí)間: 幾分鐘到幾十分鐘不等,取決于項(xiàng)目規(guī)模。
可能遇到的問題:
- 如果某些類包含特殊代碼導(dǎo)致報(bào)錯(cuò),可以先注釋掉,待升級完成后再處理
- 執(zhí)行完成后可以刪除該插件(也可以保留,以便后續(xù)增量升級)
OpenRewrite 自動(dòng)完成的變更
執(zhí)行完成后,主要變化包括:
依賴升級:
pom.xml中的依賴版本自動(dòng)升級- Spring Boot 版本升級到 3.4.x(手動(dòng)改為 3.5.4)
包名變更:
javax.servlet.* → jakarta.servlet.*javax.persistence.* → jakarta.persistence.*javax.validation.* → jakarta.validation.*
API 文檔遷移:
- Swagger 2.x → SpringDoc OpenAPI 3.x
JDK 新特性應(yīng)用:
- Text Blocks:多行字符串的優(yōu)雅處理
// 自動(dòng)轉(zhuǎn)換為
String json = """
{
"name": "user",
"age": 18
}
""";- instanceof 模式匹配:簡化類型判斷和轉(zhuǎn)換
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}- String.formatted(): 替代
String.format()
"Hello, %s!".formatted(name);
- 集合增強(qiáng): getFirst() 替代 get(0)
- @Serial 注解:標(biāo)記序列化相關(guān)字段
第三方庫升級:
- Apache HttpClient
- Apache Commons 系列
- 其他常用工具庫
增量合并場景處理
場景: 執(zhí)行 Rewrite 后,舊分支又有代碼提交,合并時(shí)出現(xiàn)大量 javax 包名和 Swagger 注解沖突。
解決方案:使用 IntelliJ IDEA 自帶的 Refactor 功能(本質(zhì)也是基于 OpenRewrite)
操作步驟:
- 打開 IDEA,選擇
Refactor → Migrate Packages and Classes - 選擇遷移規(guī)則(javax → jakarta)
- 預(yù)覽變更并執(zhí)行


第二階段:環(huán)境切換(JDK 21 環(huán)境)
重要分界線:以下操作需在 JDK 21 環(huán)境下進(jìn)行。
6. 修改 IDEA 項(xiàng)目配置
修改 SDK 和 Language Level(快捷鍵:Ctrl + Alt + Shift + S):

修改 Modules 的 Language Level:

修改 Java Compiler(快捷鍵:Ctrl + Alt + S):

核心問題與解決方案
問題一:Hibernate DDL Auto 的陷阱
嚴(yán)重警告:在完成以下配置前,切勿啟動(dòng)項(xiàng)目!否則可能導(dǎo)致數(shù)據(jù)庫結(jié)構(gòu)被錯(cuò)誤修改。
問題背景
新舊版本 Hibernate 的行為差異:

為什么要禁用?
在生產(chǎn)環(huán)境中使用 spring.jpa.hibernate.ddl-auto=update 存在嚴(yán)重風(fēng)險(xiǎn):
- 數(shù)據(jù)安全風(fēng)險(xiǎn):自動(dòng)更新可能導(dǎo)致意外的數(shù)據(jù)丟失或結(jié)構(gòu)變更
- 性能問題:啟動(dòng)時(shí)全表檢查會顯著增加應(yīng)用啟動(dòng)時(shí)間
- 版本控制缺失:無法追蹤數(shù)據(jù)庫變更歷史,不利于團(tuán)隊(duì)協(xié)作和回滾
- 升級后風(fēng)險(xiǎn)更高:Hibernate 6.x 的校驗(yàn)更嚴(yán)格,誤操作概率增加
解決方案
方案一:配置優(yōu)先級控制(推薦)
在 CI/CD 啟動(dòng)腳本中設(shè)置 VM 參數(shù):
java -jar app.jar -Dspring.jpa.hibernate.ddl-auto=none
優(yōu)先級:VM 參數(shù) > 配置中心(Apollo/Nacos) > application.properties
方案二:使用專業(yè)的數(shù)據(jù)庫版本管理工具
推薦使用 Flyway 或 Liquibase 管理數(shù)據(jù)庫腳本:
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency>
方案三:結(jié)構(gòu)對比工具
- Navicat:提供結(jié)構(gòu)同步功能
- DataGrip:IntelliJ 系產(chǎn)品,支持?jǐn)?shù)據(jù)庫結(jié)構(gòu)對比
問題二:Spring Security 配置遷移
核心變化
WebSecurityConfigurerAdapter已廢棄- 推薦使用 Lambda DSL 配置方式
- 配置方式從繼承改為 Bean 注冊
遷移示例
舊版配置(Spring Security 5.x):
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated();
}
}新版配置(Spring Security 6.x):
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement -> sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
// 允許所有 OPTIONS 請求
.requestMatchers(OPTIONS, "**").permitAll()
.requestMatchers(
"/swagger-ui/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/images/**",
"/webjars/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(new JWTFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}RequestMatcher 調(diào)整注意事項(xiàng)
- 新增 SpringDoc 路徑(必須)
/swagger-ui/** /v3/api-docs/**
修正通配符寫法:
? 錯(cuò)誤: //**/*.js ? 正確: /**/*.js 否則會拋出 PatternParseException
問題三:SpringDoc OpenAPI 配置
Swagger → SpringDoc 遷移
<!-- 移除舊的 Swagger 依賴 -->
<!--
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
-->
<!-- 添加新的 SpringDoc 依賴 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>配置示例
@Configuration
@OpenAPIDefinition
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
OpenAPI openAPI = new OpenAPI();
openAPI.info(new Info().title("API 文檔").version("1.0"));
// 配置 Authorization 登錄鑒權(quán)
Map<String, SecurityScheme> map = Map.of("Authorization",
new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.in(SecurityScheme.In.HEADER)
.name("Authorization"));
openAPI.components(new Components().securitySchemes(map));
map.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
return openAPI;
}
}注解對應(yīng)關(guān)系
| Swagger 2.x | SpringDoc OpenAPI 3.x |
|---|---|
| @Api | @Tag |
| @ApiOperation | @Operation |
| @ApiParam | @Parameter |
| @ApiModel | @Schema |
| @ApiModelProperty | @Schema |
訪問地址變更
原 Swagger UI 地址: http://localhost:8080/swagger-ui.html
新 SpringDoc 地址: http://localhost:8080/swagger-ui/index.html

問題四:依賴沖突與安全漏洞修復(fù)
檢測工具
使用 IDEA 自帶的依賴分析工具:

必須升級的依賴(存在高危漏洞)
推薦使用 OWASP Dependency-Check 或 Snyk 掃描:
mvn dependency-check:check
解決依賴沖突的技巧
問題:Maven 依賴解析采用"最短路徑優(yōu)先"和"第一聲明優(yōu)先"原則,可能導(dǎo)致舊版本覆蓋新版本。
解決方案:顯式聲明期望的版本
<dependencies>
<!-- 顯式聲明 Spring Framework 版本,避免被傳遞依賴覆蓋 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.1.3</version>
</dependency>
</dependencies>快速檢測技巧:
在 IDEA 的 Maven 依賴樹中搜索 RELEASE ,Spring 新版本已不使用 RELEASE 后綴,搜索到的基本都是舊版本。

問題五:URL 尾斜杠匹配策略變更
行為變化
| 版本 | 行為 |
|---|---|
| Spring Boot 2.x | /api/user/get 和 /api/user/get/ 視為同一接口 |
| Spring Boot 3.x | /api/user/get 和 /api/user/get/ 視為不同接口 |
常見導(dǎo)致尾斜杠的情況
- Case 1:類注解帶尾斜杠
@RequestMapping("/api/user/")
public class UserController {
@PostMapping("login") // 實(shí)際路徑:/api/user/login
}
Case 2:空字符串映射
@RequestMapping("/api/user")
public class UserController {
@PostMapping("") // 實(shí)際路徑:/api/user/(帶尾斜杠!)
}
Case 3:根路徑映射
@PostMapping("/") // 實(shí)際路徑:/(帶尾斜杠)
**** 檢查方式
- IDEA Endpoints 工具窗口:查看所有端點(diǎn)
- SpringDoc UI:訪問 Swagger 頁面檢查

臨時(shí)解決方案(不推薦長期使用)
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 設(shè) 置 為 true 以 忽 略 尾 斜 杠 , 恢 復(fù) 舊 版 本 行 為
configurer.setUseTrailingSlashMatch(true);
}
}注意__: ·
setUseTrailingSlashMatch在 Spring 6.x 后已標(biāo)記為廢棄,后續(xù)版本將刪除。建議逐步修正所有端點(diǎn),去除尾斜杠。
根本解決方案
- 修正所有 Controller 的路徑映射
- 通知前端團(tuán)隊(duì)同步修改調(diào)用路徑
- 如果有硬編碼的 URL,全局搜索并修正
- 使用測試確保前后端調(diào)用正常
問題六:Apache POI / EasyExcel 升級
背景
Apache POI 舊版本(< 5.0)存在多個(gè) CVE 安全漏洞,必須升級。
推薦方案
對于新項(xiàng)目:直接使用 FastExcel
<dependency>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
<version>1.0.0</version>
</dependency>對于使用 EasyExcel 的舊項(xiàng)目:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>說明:EasyExcel 已不再維護(hù),F(xiàn)astExcel 是社區(qū)維護(hù)的替代方案,API 基本兼容。
遷移注意事項(xiàng)
EasyExcel 跨大版本升級(2.x → 4.x)API 變化較大,主要改動(dòng):
1. 監(jiān)聽器接口方法簽名調(diào)整
2. 部分工具類包路徑變更
3. 自定義轉(zhuǎn)換器需要適配新接口
建議參考官方遷移文檔:EasyExcel官方文檔 - 基于Java的Excel處理工具 | Easy Excel 官網(wǎng)
問題七:JDK 模塊化限制(–add-opens)
問題現(xiàn)象
某些依賴庫使用反射訪問 JDK 內(nèi)部 API,在 JDK 9+ 模塊化系統(tǒng)下會報(bào)錯(cuò):
InaccessibleObjectException: Unable to make field accessible: module java.base does not "opens java.net" to unnamed module
解決方案
在 IDEA 運(yùn)行配置中添加 VM 參數(shù):
開啟 VM 參數(shù)配置(默認(rèn)隱藏):

解決方案
在 IDEA 運(yùn)行配置中添加 VM 參數(shù):
開啟 VM 參數(shù)配置(默認(rèn)隱藏):

常見需要開放的模塊
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED
問題八:過期配置屬性警告
問題現(xiàn)象
啟動(dòng)時(shí)出現(xiàn)警告:
Property 'spring.xxx.yyy' is deprecated

解決方案:
- 查看 Spring Boot 官方遷移文檔
- 使用 IDEA 的智能提示查看替代屬性
- 修改配置文件( 常見過期屬性:
application.yml或配置中心)
常見過期屬性:
| 過期屬性 | 替代屬性 |
|---|---|
| spring.datasource.type | 自動(dòng)推斷,無需配置 |
| spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults | 已移除 |
| management.metrics.export.prometheus.enabled | management.prometheus.metrics.export.enabled |
完整測試清單
升級完成后,務(wù)必進(jìn)行全面的回歸測試:
- Spring Security:認(rèn)證、授權(quán)是否正常
- SpringDoc:API 文檔是否可訪問( /swagger-ui/index.html )
- 數(shù)據(jù)庫操作:JPA/MyBatis 是否正常工作
- 緩存:Redis/Caffeine 等緩存是否生效
- 消息隊(duì)列:RabbitMQ/Kafka 等是否正常
- 定時(shí)任務(wù):Scheduled/Quartz 是否按預(yù)期執(zhí)行
- 文件上傳/下載:文件 IO 操作是否正常
- 業(yè)務(wù)功能:核心業(yè)務(wù)流程是否正常(重點(diǎn)關(guān)注有代碼改動(dòng)的地方)
- 性能測試:對比升級前后的性能指標(biāo)
升級感悟
框架層面的變化趨勢
通過這次升級,我觀察到現(xiàn)代框架的一些發(fā)展趨勢:
- 校驗(yàn)更嚴(yán)格:
- Spring 不再容忍 URL 尾斜杠的模糊匹配
- 循環(huán)依賴檢測更嚴(yán)格(默認(rèn)禁止)
- Hibernate 對實(shí)體狀態(tài)的校驗(yàn)更精確
- 安全性優(yōu)先:
- 默認(rèn)配置更保守
- 廢棄不安全的 API
- 強(qiáng)制升級修復(fù)已知漏洞
- 現(xiàn)代化 API:
- Lambda DSL 配置風(fēng)格
- 函數(shù)式編程支持
- 更簡潔的 API 設(shè)計(jì)
依賴選擇建議
基于這次升級經(jīng)驗(yàn),對于第三方庫的選擇建議:
優(yōu)先選擇:
? 國際主流項(xiàng)目(Apache、Spring 生態(tài)等)
? 有完善文檔和測試的項(xiàng)目
? 活躍維護(hù)且社區(qū)規(guī)模大的項(xiàng)目
? 語義化版本管理清晰的項(xiàng)目
謹(jǐn)慎選擇:
?? 缺乏自動(dòng)化測試的項(xiàng)目
?? 長期未更新的項(xiàng)目
?? API 設(shè)計(jì)不穩(wěn)定、頻繁 Breaking Change 的項(xiàng)目
?? 文檔不全、維護(hù)團(tuán)隊(duì)不穩(wěn)定的項(xiàng)目
自動(dòng)化遷移的價(jià)值
OpenRewrite 等自動(dòng)化工具在大版本升級中的價(jià)值無可替代:
- 減少 90% 以上的機(jī)械性改動(dòng)
- 避免手工替換導(dǎo)致的遺漏
- 保持代碼風(fēng)格和注釋
- 降低升級風(fēng)險(xiǎn)
建議在日常開發(fā)中也關(guān)注此類工具,提升團(tuán)隊(duì)整體效率。
本文首發(fā)于 [尚硅谷編程聯(lián)盟],轉(zhuǎn)載請注明出處。
最后:
“在這個(gè)最后的篇章中,我要表達(dá)我對每一位讀者的感激之情。你們的關(guān)注和回復(fù)是我創(chuàng)作的動(dòng)力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵(lì)留在心底,繼續(xù)在其他的領(lǐng)域奮斗。感謝你們,我們總會在某個(gè)時(shí)刻再次相遇。”
到此這篇關(guān)于Spring Boot3 + JDK21 的遷移超詳細(xì)步驟(最新推薦)的文章就介紹到這了,更多相關(guān)springboot jdk遷移內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring boot+mybatis 多數(shù)據(jù)源切換(實(shí)例講解)
下面小編就為大家?guī)硪黄猻pring boot+mybatis 多數(shù)據(jù)源切換(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09
SpringBoot2.X Devtools熱部署實(shí)現(xiàn)解析
這篇文章主要介紹了SpringBoot2.X Devtools熱部署實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Spring的@CrossOrigin注解使用與CrossFilter對象自定義詳解
這篇文章主要介紹了Spring的@CrossOrigin注解使用與CrossFilter對象自定義詳解,跨域,指的是瀏覽器不能執(zhí)行其他網(wǎng)站的腳本,它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制,所謂同源是指,域名,協(xié)議,端口均相同,需要的朋友可以參考下2023-12-12
一文總結(jié)RabbitMQ中的消息確認(rèn)機(jī)制
RabbitMQ消息確認(rèn)機(jī)制指的是在消息傳遞過程中,發(fā)送方發(fā)送消息后,接收方需要對消息進(jìn)行確認(rèn),以確保消息被正確地接收和處理,本文為大家整理了RabbitMQ中的消息確認(rèn)機(jī)制,需要的可以參考一下2023-06-06
Mybatis中多個(gè)對象包含同一個(gè)對象的處理操作
這篇文章主要介紹了Mybatis中多個(gè)對象包含同一個(gè)對象的處理操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式
這篇文章主要介紹了Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12

