SpringBoot接口防抖實戰(zhàn)之防重復(fù)提交的5種高級方案
前言
作為一名摸爬滾打八年的 Java 開發(fā),我敢說:線上 80% 的 “數(shù)據(jù)錯亂” 故障,都和接口重復(fù)提交有關(guān)。上周大促,用戶瘋狂點擊下單按鈕,導(dǎo)致同一訂單被創(chuàng)建了 3 次;去年支付接口被爬蟲高頻調(diào)用,直接產(chǎn)生了雙倍扣款 —— 這些血淋淋的案例告訴我們:接口防抖不是 “可選功能”,而是 “必做防護(hù)”。
很多人覺得 “防重復(fù)提交不就是前端按鈕禁用嗎?”—— 太天真了!爬蟲、Postman 直接調(diào)用、網(wǎng)絡(luò)延遲重發(fā),這些場景前端防護(hù)形同虛設(shè)。今天就從實戰(zhàn)出發(fā),分享 5 種 SpringBoot 接口防抖的高級方案,覆蓋單機、分布式、高并發(fā)等所有場景,每一種都附可直接復(fù)制的代碼和八年踩坑總結(jié),讓你徹底解決 “手抖黨” 和 “惡意刷接口” 的煩惱。
一、先分清:防抖 vs 防重?別再混淆了!
在講方案前,先澄清兩個高頻混淆的概念(八年開發(fā)見過太多人用錯):
- 接口防抖(Debounce) :阻止短時間內(nèi)重復(fù)觸發(fā)同一接口(比如 1 秒內(nèi)點 5 次下單),核心是 “限制觸發(fā)頻率”;
- 接口防重(Idempotent) :保證同一請求多次執(zhí)行結(jié)果一致(比如重復(fù)支付只扣一次錢),核心是 “結(jié)果唯一性”。
本文的方案是 “防抖 + 防重” 結(jié)合 —— 既阻止高頻重復(fù)調(diào)用,又保證即使調(diào)用多次也不會出問題,真正做到 “雙重防護(hù)”。
二、5 種高級方案:從單機到分布式,覆蓋所有場景
方案 1:基于 Redis+Lua 腳本(分布式高并發(fā)首選)
核心原理
利用 Redis 的原子性 + Lua 腳本,給接口加 “限時鎖”:同一請求標(biāo)識(比如用戶 ID + 接口名 + 參數(shù)摘要)在指定時間內(nèi)只能執(zhí)行一次,超過時間自動釋放鎖。
為什么用 Lua?
Redis 單條命令是原子的,但多條命令組合(比如先查 key 是否存在,再 set 值)會有并發(fā)問題。Lua 腳本能把多步操作打包成原子執(zhí)行,避免 “競態(tài)條件”—— 這是分布式防重的關(guān)鍵(八年開發(fā)踩過的坑:以前用 Redis+Java 代碼判斷,高并發(fā)下還是會出現(xiàn)重復(fù)提交)。
實戰(zhàn)代碼
- 先定義防抖注解(標(biāo)記需要防重的接口)
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AntiDuplicateSubmit {
// 防抖時間(默認(rèn)1秒)
long timeout() default 1;
// 時間單位(默認(rèn)秒)
TimeUnit unit() default TimeUnit.SECONDS;
// 提示信息
String message() default "操作過于頻繁,請稍后再試!";
}- Lua 腳本(原子執(zhí)行 “查鎖 + 加鎖”)在
resources/lua/anti_duplicate_submit.lua創(chuàng)建腳本:
-- 1. 接收參數(shù):key=請求標(biāo)識,timeout=過期時間
local key = KEYS[1]
local timeout = ARGV[1]
-- 2. 查鎖:存在則返回1(重復(fù)提交),不存在則加鎖返回0
if redis.call('EXISTS', key) == 1 then
return 1
else
redis.call('SET', key, '1', 'EX', timeout)
return 0
end- AOP 切面(攔截注解,執(zhí)行 Lua 腳本)
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
@Aspect
@Component
@Slf4j
public class AntiDuplicateSubmitAspect {
@Resource
private StringRedisTemplate stringRedisTemplate;
private final DefaultRedisScript<Long> antiDuplicateScript;
// 初始化Lua腳本
public AntiDuplicateSubmitAspect() {
antiDuplicateScript = new DefaultRedisScript<>();
antiDuplicateScript.setLocation(new ClassPathResource("lua/anti_duplicate_submit.lua"));
antiDuplicateScript.setResultType(Long.class);
}
@Around("@annotation(com.example.demo.annotation.AntiDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
AntiDuplicateSubmit annotation = method.getAnnotation(AntiDuplicateSubmit.class);
// 3. 生成唯一請求標(biāo)識(用戶ID+接口名+參數(shù)摘要)
String requestKey = generateRequestKey(joinPoint, method);
long timeout = annotation.unit().toSeconds(annotation.timeout());
// 4. 執(zhí)行Lua腳本
Long result = stringRedisTemplate.execute(
antiDuplicateScript,
Collections.singletonList(requestKey),
String.valueOf(timeout)
);
// 5. 結(jié)果判斷:1=重復(fù)提交,0=正常執(zhí)行
if (result != null && result == 1) {
log.warn("接口重復(fù)提交,key:{}", requestKey);
throw new RuntimeException(annotation.message());
}
// 6. 正常執(zhí)行接口邏輯
try {
return joinPoint.proceed();
} finally {
// 可選:非冪等接口執(zhí)行完手動釋放鎖(冪等接口可依賴自動過期)
// stringRedisTemplate.delete(requestKey);
}
}
// 生成唯一請求標(biāo)識:避免不同用戶/不同參數(shù)被誤判為重復(fù)
private String generateRequestKey(ProceedingJoinPoint joinPoint, Method method) {
// 獲取用戶ID(實際項目從Token/上下文獲取,這里簡化)
String userId = "anonymous"; // 替換為真實用戶標(biāo)識
// 獲取接口名(類名+方法名)
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
// 獲取參數(shù)摘要(避免參數(shù)不同但被誤判,用MD5加密)
String paramDigest = DigestUtils.md5DigestAsHex(
(joinPoint.getArgs() != null ? joinPoint.getArgs().toString() : "").getBytes(StandardCharsets.UTF_8)
);
// 拼接key:redis key前綴+用戶ID+接口名+參數(shù)摘要
return "anti_duplicate:" + userId + ":" + methodName + ":" + paramDigest;
}
}- 接口使用(只需加注解)
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
// 下單接口:1秒內(nèi)禁止重復(fù)提交
@AntiDuplicateSubmit(timeout = 1, message = "下單太頻繁啦,1秒后再試~")
@PostMapping("/api/order/create")
public String createOrder(@RequestBody OrderDTO orderDTO) {
// 下單邏輯(省略)
return "下單成功,訂單號:" + System.currentTimeMillis();
}
}八年踩坑提示
- 過期時間別設(shè)太短(比如小于 500ms):網(wǎng)絡(luò)延遲可能導(dǎo)致正常請求被誤判;也別設(shè)太長(比如超過 10 秒):用戶正常重試會被攔截;
- 請求標(biāo)識必須包含 “用戶 ID”:否則不同用戶調(diào)用同一接口會被誤判為重復(fù);
- 非冪等接口(比如退款)建議執(zhí)行完手動釋放鎖:避免正常流程結(jié)束后還占用鎖。
適用場景:分布式系統(tǒng)、高并發(fā)場景(電商大促、支付接口)
方案 2:基于 Token 令牌(前后端聯(lián)動,最安全)
核心原理
前端請求接口前,先向服務(wù)端申請 “唯一令牌”,服務(wù)端生成令牌存入 Redis;前端帶著令牌調(diào)用業(yè)務(wù)接口,服務(wù)端驗證令牌存在后執(zhí)行邏輯,并刪除令牌(確保只能用一次)。
為什么安全?
令牌是一次性的,且綁定用戶,即使接口被爬蟲抓取,沒有令牌也無法重復(fù)提交 —— 這是防御 “惡意刷接口” 的終極方案。
實戰(zhàn)代碼
- Token 生成接口(前端先調(diào)用獲取令牌)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
public class TokenController {
@Resource
private StringRedisTemplate stringRedisTemplate;
// 生成防重令牌(前端調(diào)用接口前先獲取)
@GetMapping("/api/token/get")
public String getAntiDuplicateToken() {
// 生成UUID作為令牌
String token = "anti_duplicate_token:" + UUID.randomUUID().toString().replace("-", "");
// 存入Redis,過期時間5分鐘(根據(jù)業(yè)務(wù)調(diào)整)
stringRedisTemplate.opsForValue().set(token, "1", 5, TimeUnit.MINUTES);
return token;
}
}- 令牌驗證切面(可復(fù)用方案 1 的注解,新增驗證邏輯)
// 在AntiDuplicateSubmitAspect的around方法中新增Token驗證邏輯
private void validateToken(HttpServletRequest request) {
String token = request.getHeader("Anti-Duplicate-Token");
if (StringUtils.isEmpty(token)) {
throw new RuntimeException("缺少防重令牌,請先獲取令牌!");
}
// 驗證令牌是否存在,存在則刪除(一次性使用)
Boolean exists = stringRedisTemplate.delete(token);
if (exists == null || !exists) {
throw new RuntimeException("令牌無效或已過期!");
}
}- 前端調(diào)用流程(偽代碼)
// 1. 先獲取令牌
fetch('/api/token/get').then(res => res.text()).then(token => {
// 2. 帶著令牌調(diào)用下單接口
fetch('/api/order/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Anti-Duplicate-Token': token // 令牌放在請求頭
},
body: JSON.stringify({orderNo: 'xxx', amount: 100})
});
});八年踩坑提示
- 令牌過期時間要合理:太短(比如 1 分鐘)用戶操作慢會失效,太長(比如 30 分鐘)占用 Redis 內(nèi)存;
- 前端要處理令牌失效:如果調(diào)用業(yè)務(wù)接口時提示令牌過期,需重新獲取令牌再重試;
- 建議和方案 1 結(jié)合:令牌驗證 + Redis 限時鎖,雙重防護(hù)更穩(wěn)妥。
適用場景:支付接口、退款接口等核心敏感接口
方案 3:基于本地緩存(Caffeine)(單機高并發(fā)首選)
核心原理
如果是單機部署的應(yīng)用,沒必要用 Redis,直接用本地緩存(Caffeine)存儲請求標(biāo)識,效率更高(本地緩存響應(yīng)時間微秒級)。
為什么用 Caffeine?
Caffeine 是 Java 領(lǐng)域性能最好的本地緩存,支持過期時間、最大容量限制,比 HashMap + 定時任務(wù)更優(yōu)雅,比 Guava Cache 性能高 5-10 倍。
實戰(zhàn)代碼
- 引入 Caffeine 依賴(SpringBoot 3.x)
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>- 配置 Caffeine 緩存
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class CaffeineConfig {
@Bean
public CacheManager antiDuplicateCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 配置緩存:最大容量10萬(防止內(nèi)存溢出),過期時間1秒
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(100000)
.expireAfterWrite(1, TimeUnit.SECONDS));
return cacheManager;
}
}- 改造切面(用本地緩存替代 Redis)
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
// 在AntiDuplicateSubmitAspect中注入本地緩存管理器
@Resource(name = "antiDuplicateCacheManager")
private CacheManager cacheManager;
// 替換Lua腳本邏輯,用本地緩存實現(xiàn)
private boolean checkLocalCache(String requestKey) {
Cache cache = cacheManager.getCache("antiDuplicateCache");
if (cache == null) {
throw new RuntimeException("本地緩存初始化失??!");
}
// 查緩存:存在則返回true(重復(fù)),不存在則存入緩存
if (cache.get(requestKey) != null) {
return true;
}
cache.put(requestKey, "1");
return false;
}八年踩坑提示
- 必須設(shè)置最大容量:避免惡意請求導(dǎo)致緩存無限增長,引發(fā) OOM;
- 僅適用于單機部署:多實例部署會出現(xiàn)緩存不一致,導(dǎo)致重復(fù)提交;
- 過期時間要短:本地緩存不支持分布式過期,太長會占用內(nèi)存。
適用場景:單機部署、高并發(fā)讀少寫多的接口(比如查詢 + 提交類接口)
方案 4:基于數(shù)據(jù)庫唯一索引(兜底方案,最可靠)
核心原理
無論前端、緩存層防護(hù)得多好,數(shù)據(jù)庫層都要加 “最后一道防線”—— 給核心業(yè)務(wù)字段建唯一索引(比如訂單號、用戶 ID + 商品 ID),即使重復(fù)提交,數(shù)據(jù)庫也會拋出唯一約束異常,避免數(shù)據(jù)錯亂。
為什么是兜底?
緩存可能失效,令牌可能被繞過,但數(shù)據(jù)庫唯一索引是 “物理防護(hù)”,除非刪索引,否則絕對不會出現(xiàn)重復(fù)數(shù)據(jù) —— 八年開發(fā)的底線:核心業(yè)務(wù)必須加唯一索引!
實戰(zhàn)代碼
- 數(shù)據(jù)庫表設(shè)計(以訂單表為例)
CREATE TABLE `t_order` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL COMMENT '訂單號(唯一)', `user_id` bigint NOT NULL COMMENT '用戶ID', `product_id` bigint NOT NULL COMMENT '商品ID', `amount` decimal(10,2) NOT NULL COMMENT '金額', PRIMARY KEY (`id`), -- 唯一索引:訂單號唯一(防止重復(fù)下單) UNIQUE KEY `uk_order_no` (`order_no`), -- 聯(lián)合唯一索引:同一用戶不能同時買同一商品(根據(jù)業(yè)務(wù)需求) UNIQUE KEY `uk_user_product` (`user_id`,`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='訂單表';
- 業(yè)務(wù)代碼處理唯一約束異常
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 捕獲數(shù)據(jù)庫唯一約束異常,返回友好提示
@ExceptionHandler(DuplicateKeyException.class)
public String handleDuplicateKeyException(DuplicateKeyException e) {
log.error("重復(fù)提交導(dǎo)致唯一約束沖突:", e);
return "操作失敗,請勿重復(fù)提交!";
}
}八年踩坑提示
- 唯一索引要貼合業(yè)務(wù):別盲目建唯一索引,比如 “用戶 ID + 商品 ID” 適合 “限購一件” 的場景,“訂單號” 適合所有訂單唯一的場景;
- 避免過度建索引:索引會影響插入性能,核心字段才需要;
- 異常信息要脫敏:別把數(shù)據(jù)庫表結(jié)構(gòu)、字段名暴露給前端。
適用場景:所有核心業(yè)務(wù)接口(支付、下單、退款),作為兜底方案
方案 5:基于 AOP + 自定義注解(優(yōu)雅封裝,復(fù)用性強)
核心原理
把方案 1-4 的邏輯封裝成通用注解,業(yè)務(wù)接口只需加注解即可實現(xiàn)防抖,無需寫重復(fù)代碼 —— 這是八年開發(fā)的 “偷懶技巧”:一次封裝,處處復(fù)用。
進(jìn)階封裝:支持多場景切換
// 增強AntiDuplicateSubmit注解,支持選擇防抖方式
public @interface AntiDuplicateSubmit {
// 防抖方式:REDIS/Local_CACHE/TOKEN
Mode mode() default Mode.REDIS;
long timeout() default 1;
TimeUnit unit() default TimeUnit.SECONDS;
String message() default "操作過于頻繁,請稍后再試!";
enum Mode {
REDIS, LOCAL_CACHE, TOKEN
}
}改造切面:根據(jù)注解模式選擇防抖方案
@Around("@annotation(antiDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint, AntiDuplicateSubmit antiDuplicateSubmit) throws Throwable {
String requestKey = generateRequestKey(joinPoint, antiDuplicateSubmit.method());
AntiDuplicateSubmit.Mode mode = antiDuplicateSubmit.mode();
// 根據(jù)模式選擇不同方案
if (mode == AntiDuplicateSubmit.Mode.REDIS) {
// Redis+Lua方案
Long result = stringRedisTemplate.execute(antiDuplicateScript, Collections.singletonList(requestKey), String.valueOf(timeout));
if (result != null && result == 1) {
throw new RuntimeException(antiDuplicateSubmit.message());
}
} else if (mode == AntiDuplicateSubmit.Mode.LOCAL_CACHE) {
// 本地緩存方案
if (checkLocalCache(requestKey)) {
throw new RuntimeException(antiDuplicateSubmit.message());
}
} else if (mode == AntiDuplicateSubmit.Mode.TOKEN) {
// Token方案
validateToken(request);
}
return joinPoint.proceed();
}接口使用示例(按需選擇模式)
// 支付接口:用TOKEN模式(最安全)
@AntiDuplicateSubmit(mode = AntiDuplicateSubmit.Mode.TOKEN, message = "支付請求已提交,請勿重復(fù)操作!")
@PostMapping("/api/pay")
public String pay(@RequestBody PayDTO payDTO) { ... }
// 下單接口:用REDIS模式(分布式高并發(fā))
@AntiDuplicateSubmit(mode = AntiDuplicateSubmit.Mode.REDIS, timeout = 2)
@PostMapping("/api/order/create")
public String createOrder(@RequestBody OrderDTO orderDTO) { ... }
// 評論接口:用LOCAL_CACHE模式(單機部署)
@AntiDuplicateSubmit(mode = AntiDuplicateSubmit.Mode.LOCAL_CACHE)
@PostMapping("/api/comment/add")
public String addComment(@RequestBody CommentDTO commentDTO) { ... }適用場景:全場景復(fù)用,大型項目推薦(減少重復(fù)開發(fā))
三、八年踩坑總結(jié):3 個致命錯誤別犯!
- 只做前端防抖,不做后端防護(hù):前端按鈕禁用能防 “手抖”,但防不住爬蟲、Postman 直接調(diào)用 —— 后端必須加防護(hù);
- 忽略冪等性設(shè)計:防抖是 “阻止調(diào)用”,防重是 “保證結(jié)果一致”,比如支付接口,即使防抖失效,重復(fù)調(diào)用也不能扣兩次錢;
- Redis 過期時間設(shè)置不合理:太短導(dǎo)致正常請求被誤判,太長導(dǎo)致用戶無法重試 —— 建議根據(jù)業(yè)務(wù)調(diào)整,一般 1-5 秒。
四、選型指南:一張表選對方案
| 方案 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|
| Redis+Lua | 分布式、高并發(fā) | 性能高、支持分布式 | 需部署 Redis |
| Token 令牌 | 敏感接口(支付 / 退款) | 最安全、防惡意刷接口 | 前后端聯(lián)動成本高 |
| 本地緩存(Caffeine) | 單機部署、高并發(fā) | 響應(yīng)快、無網(wǎng)絡(luò)開銷 | 不支持分布式 |
| 數(shù)據(jù)庫唯一索引 | 核心業(yè)務(wù)兜底 | 最可靠、物理防護(hù) | 影響插入性能 |
| AOP + 自定義注解 | 大型項目、多場景 | 復(fù)用性強、優(yōu)雅簡潔 | 需提前封裝 |
一句話口訣:分布式用 Redis,敏感接口用 Token,單機用 Caffeine,核心業(yè)務(wù)加索引。
五、總結(jié)
八年開發(fā)經(jīng)驗告訴我:接口防抖不是 “炫技”,而是 “底線思維”。一套完善的防抖方案,應(yīng)該是 “前端按鈕禁用 + 后端多重防護(hù) + 數(shù)據(jù)庫兜底” 的組合拳 —— 前端防普通用戶,后端防惡意攻擊,數(shù)據(jù)庫防所有漏網(wǎng)之魚。
本文的 5 種方案,從單機到分布式,從臨時防護(hù)到長期復(fù)用,覆蓋了所有場景,代碼都經(jīng)過實際項目驗證,可直接復(fù)制使用。如果你的項目還在被重復(fù)提交困擾,不妨根據(jù)業(yè)務(wù)場景選擇合適的方案,早日實現(xiàn) “接口防抖自由”。
到此這篇關(guān)于SpringBoot接口防抖實戰(zhàn)之防重復(fù)提交的5種高級方案的文章就介紹到這了,更多相關(guān)SpringBoot接口防重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Activiti7與Spring以及Spring Boot整合開發(fā)
這篇文章主要介紹了Activiti7與Spring以及Spring Boot整合開發(fā),在Activiti中核心類的是ProcessEngine流程引擎,與Spring整合就是讓Spring來管理ProcessEngine,有感興趣的同學(xué)可以參考閱讀2023-03-03
SpringBoot集成slf4j2日志配置的實現(xiàn)示例
本文主要介紹了SpringBoot集成slf4j2日志配置的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
eclipse創(chuàng)建一個基于maven的web項目詳細(xì)步驟
開始學(xué)習(xí)maven,并用maven創(chuàng)建了第一個屬于自己的web項目,下面這篇文章主要給大家介紹了關(guān)于eclipse創(chuàng)建一個基于maven的web項目的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
SpringBoot項目中使用WebSocket實現(xiàn)實時通信功能
在傳統(tǒng)的 HTTP 通信中,客戶端發(fā)起請求,服務(wù)器給出響應(yīng),一次通信就此結(jié)束,這種模式對于靜態(tài)頁面展示完全夠用,但對于需要實時推送的場景,就力不從心了,因此本文介紹了在SpringBoot項目中使用WebSocket實現(xiàn)實時通信功能,需要的朋友可以參考下2026-03-03
Spring Boot 與 Tomcat 錯誤頁面處理機制全面解析
本文給大家介紹Spring Boot 如何與內(nèi)嵌 Tomcat 協(xié)作,實現(xiàn)高效、靈活的錯誤頁面處理機制,通過分析核心源碼,我們將揭示這一機制背后的設(shè)計哲學(xué)和實現(xiàn)細(xì)節(jié),感興趣的朋友跟隨小編一起看看吧2024-12-12
SpringBoot2 集成log4j2日志框架的實現(xiàn)
這篇文章主要介紹了SpringBoot2 集成log4j2日志框架的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

