SpringBoot中統(tǒng)一同步與異步執(zhí)行模型的示例詳解
背景
在項(xiàng)目開(kāi)發(fā)中,只要同時(shí)使用同步和異步執(zhí)行邏輯,很容易出現(xiàn)代碼混亂的問(wèn)題,一開(kāi)始大家覺(jué)得這只是“執(zhí)行方式不同”,沒(méi)什么大不了。但隨著業(yè)務(wù)復(fù)雜度上升,問(wèn)題逐漸暴露:
1、同步能實(shí)時(shí)拿到結(jié)果、捕獲異常,異步失敗了只能靠日志排查;
2、請(qǐng)求ID、操作人這些上下文信息,在異步執(zhí)行時(shí)經(jīng)常“丟失”;
3、新入隊(duì)同學(xué)需要刻意記住每個(gè)方法是同步還是異步,稍不注意就踩坑;
4、日志、審計(jì)、監(jiān)控這些通用邏輯,同步和異步要各加一遍。
其實(shí)根源不是“不能用異步”,而是大家把同步和異步當(dāng)成了兩套完全獨(dú)立的執(zhí)行體系——但對(duì)業(yè)務(wù)來(lái)說(shuō),它們本質(zhì)上都是“一次業(yè)務(wù)執(zhí)行”。
解決方案
換個(gè)思路:先把“業(yè)務(wù)執(zhí)行”本身抽象出來(lái),再用統(tǒng)一的引擎來(lái)處理“執(zhí)行方式”。
也就是說(shuō),業(yè)務(wù)只需要關(guān)心“做什么”,不需要關(guān)心“怎么執(zhí)行”;至于是同步還是異步,交給底層引擎決定。
這樣做的好處很明確:
- 業(yè)務(wù)代碼更聚焦,不再混雜執(zhí)行細(xì)節(jié);
- 同步/異步用同一種模型,上下文、異常、日志自然統(tǒng)一;
- 未來(lái)切換執(zhí)行方式或擴(kuò)展能力(重試、限流等),無(wú)需修改業(yè)務(wù)代碼。
技術(shù)實(shí)現(xiàn)
1. 定義“執(zhí)行單元”:業(yè)務(wù)的最小執(zhí)行單位
先抽象出一個(gè)最簡(jiǎn)潔的接口,代表“一次業(yè)務(wù)執(zhí)行”:
public interface Execution<R> {
R execute(ExecutionContext context) throws Exception;
}
這個(gè)接口只包含業(yè)務(wù)關(guān)心的元素:有上下文(ExecutionContext)、有執(zhí)行結(jié)果(R)、可能拋出異常。它不關(guān)心任何執(zhí)行細(xì)節(jié)——同步還是異步、用哪個(gè)線程池、怎么調(diào)度,都和它沒(méi)關(guān)系。
2. 統(tǒng)一“執(zhí)行上下文”:讓信息在執(zhí)行中流動(dòng)
上下文是業(yè)務(wù)執(zhí)行的“環(huán)境變量”,我們用ThreadLocal實(shí)現(xiàn)上下文的跨線程傳遞:
public class ExecutionContext {
// 用 ThreadLocal 存儲(chǔ)當(dāng)前上下文
private static final ThreadLocal<ExecutionContext> CONTEXT_HOLDER =
ThreadLocal.withInitial(ExecutionContext::new);
private String requestId; // 鏈路跟蹤用的請(qǐng)求ID
private String operator; // 操作人,用于審計(jì)
private Map<String, Object> attributes = new HashMap<>(); // 業(yè)務(wù)參數(shù)
/** 獲取當(dāng)前上下文 */
public static ExecutionContext current() {
return CONTEXT_HOLDER.get();
}
/** 設(shè)置上下文 */
public static void set(ExecutionContext context) {
CONTEXT_HOLDER.set(context);
}
/** 清理上下文 */
public static void clear() {
CONTEXT_HOLDER.remove();
}
// getter/setter 省略
}
3.上下文的實(shí)際使用流程
請(qǐng)求入口設(shè)置上下文
在請(qǐng)求入口(如 Controller/Filter)統(tǒng)一初始化和設(shè)置上下文:
@RestController
public class OrderController {
@PostMapping("/order")
public String createOrder(@RequestBody Order order) {
// 1. 構(gòu)建上下文
ExecutionContext context = new ExecutionContext();
context.setRequestId(UUID.randomUUID().toString()); // 生成或從請(qǐng)求頭獲取
context.setOperator("currentUser"); // 從登錄信息獲取
context.getAttributes().put("order", order);
// 2. 綁定到當(dāng)前線程
ExecutionContext.set(context);
try {
// 3. 提交業(yè)務(wù)執(zhí)行(同步/異步由引擎決定)
ExecutionResult<Boolean> result = executorEngine.submit(ctx -> {
Order orderFromCtx = ctx.getAttribute("order");
orderService.createOrder(orderFromCtx);
return true;
});
return "success";
} finally {
// 4. 請(qǐng)求結(jié)束清理上下文,避免內(nèi)存泄漏
ExecutionContext.clear();
}
}
}
實(shí)現(xiàn)“執(zhí)行引擎”:統(tǒng)一的執(zhí)行入口
有了執(zhí)行單元和上下文,接下來(lái)需要一個(gè)統(tǒng)一的入口來(lái)執(zhí)行它們:
public interface ExecutorEngine {
<R> ExecutionResult<R> submit(Execution<R> execution);
}
業(yè)務(wù)側(cè)只需要提交執(zhí)行單元,不需要關(guān)心執(zhí)行方式。執(zhí)行引擎會(huì)根據(jù)配置自動(dòng)處理同步或異步。
同步執(zhí)行引擎:最簡(jiǎn)單的實(shí)現(xiàn),直接在當(dāng)前線程執(zhí)行
public class SyncExecutorEngine implements ExecutorEngine {
@Override
public <R> ExecutionResult<R> submit(Execution<R> execution) {
try {
R result = execution.execute(ExecutionContext.current());
return ExecutionResult.success(result);
} catch (Exception e) {
return ExecutionResult.failure(e);
}
}
}
異步執(zhí)行引擎:復(fù)用線程池,支持上下文傳遞
public class AsyncExecutorEngine implements ExecutorEngine {
private final Executor executor;
public AsyncExecutorEngine(Executor executor) {
this.executor = executor;
}
@Override
public <R> ExecutionResult<R> submit(Execution<R> execution) {
// 捕獲當(dāng)前線程上下文并傳遞到異步線程
ExecutionContext currentCtx = ExecutionContext.current();
CompletableFuture<R> future = CompletableFuture.supplyAsync(() -> {
try {
ExecutionContext.set(currentCtx);
return execution.execute(ExecutionContext.current());
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 清理上下文避免內(nèi)存泄漏
ExecutionContext.clear();
}
}, executor);
return ExecutionResult.async(future);
}
}
業(yè)務(wù)側(cè)的使用方式
業(yè)務(wù)只需要把原來(lái)的代碼封裝成執(zhí)行單元,交給引擎處理:
// 原來(lái)的同步/異步代碼無(wú)需再維護(hù)兩套
executorEngine.submit(ctx -> {
Order order = ctx.getAttribute("order");
orderService.createOrder(order);
return true;
});
這段代碼:
- 不關(guān)心是同步還是異步執(zhí)行;
- 上下文自動(dòng)傳遞;
- 異常由引擎統(tǒng)一處理;
- 日志、審計(jì)等通用邏輯在引擎層一次性處理,自動(dòng)生效。
總結(jié)
通過(guò)統(tǒng)一執(zhí)行模型的設(shè)計(jì),我們可以將 業(yè)務(wù)邏輯 與 執(zhí)行方式清晰分離,這套模型在項(xiàng)目中能解決幾個(gè)實(shí)際問(wèn)題:
1. 代碼可控:不再到處是 @Async 和 CompletableFuture,所有執(zhí)行都有統(tǒng)一入口;
2. 上下文不丟:請(qǐng)求ID、操作人等信息在同步/異步中都能正確傳遞;
3. 擴(kuò)展方便:要加重試、限流、熔斷?在引擎層統(tǒng)一實(shí)現(xiàn)即可,業(yè)務(wù)代碼無(wú)需修改;
4. 降低認(rèn)知成本:新同學(xué)不需要刻意區(qū)分同步/異步方法,調(diào)用方式完全一致。
到此這篇關(guān)于SpringBoot中統(tǒng)一同步與異步執(zhí)行模型的示例詳解的文章就介紹到這了,更多相關(guān)SpringBoot統(tǒng)一同步與異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于ZooKeeper實(shí)現(xiàn)隊(duì)列源碼
這篇文章主要介紹了基于ZooKeeper實(shí)現(xiàn)隊(duì)列源碼的相關(guān)內(nèi)容,包括其實(shí)現(xiàn)原理和應(yīng)用場(chǎng)景,以及對(duì)隊(duì)列的簡(jiǎn)單介紹,具有一定參考價(jià)值,需要的朋友可以了解下。2017-09-09
SpringBoot整合Mybatis自定義攔截器不起作用的處理方案
這篇文章主要介紹了SpringBoot整合Mybatis自定義攔截器不起作用的處理方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Java進(jìn)階之高并發(fā)核心Selector詳解
前幾篇文章介紹了Java高并發(fā)的一些基礎(chǔ)內(nèi)容,認(rèn)識(shí)了Channel,Buffer和Selector的基本用法,有了感性認(rèn)識(shí)之后,來(lái)看看Selector的底層是如何實(shí)現(xiàn)的。,需要的朋友可以參考下2021-05-05
JavaWeb響應(yīng)下載功能實(shí)例代碼(包含工具類(lèi))
今天通過(guò)本文給大家分享的是關(guān)于javaweb的響應(yīng)(response)下載功能,需要的朋友參考下吧2017-07-07
java通過(guò)控制鼠標(biāo)實(shí)現(xiàn)屏幕廣播的方法
這篇文章主要介紹了java通過(guò)控制鼠標(biāo)實(shí)現(xiàn)屏幕廣播的方法,針對(duì)前面一篇Java屏幕共享功能進(jìn)行了改進(jìn),實(shí)現(xiàn)了鼠標(biāo)控制功能,具有一定的實(shí)用價(jià)值,需要的朋友可以參考下2014-12-12
spring boot創(chuàng)建和數(shù)據(jù)庫(kù)關(guān)聯(lián)模塊詳解
這篇文章主要給大家介紹了關(guān)于spring boot創(chuàng)建和數(shù)據(jù)庫(kù)關(guān)聯(lián)模塊的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

