Spring Boot 中统一同步与异步执行模型

背景

在项目开发中,只要同时使用同步和异步执行逻辑,很容易出现代码混乱的问题,一开始大家觉得这只是"执行方式不同",没什么大不了。但随着业务复杂度上升,问题逐渐暴露:

1、同步能实时拿到结果、捕获异常,异步失败了只能靠日志排查;

2、请求ID、操作人这些上下文信息,在异步执行时经常"丢失";

3、新入队同学需要刻意记住每个方法是同步还是异步,稍不注意就踩坑;

4、日志、审计、监控这些通用逻辑,同步和异步要各加一遍。

其实根源不是"不能用异步",而是大家把同步和异步当成了两套完全独立的执行体系------但对业务来说,它们本质上都是"一次业务执行"。

解决方案

换个思路:先把"业务执行"本身抽象出来,再用统一的引擎来处理"执行方式"。

也就是说,业务只需要关心"做什么",不需要关心"怎么执行";至于是同步还是异步,交给底层引擎决定。

这样做的好处很明确:

  • 业务代码更聚焦,不再混杂执行细节;
  • 同步/异步用同一种模型,上下文、异常、日志自然统一;
  • 未来切换执行方式或扩展能力(重试、限流等),无需修改业务代码。

技术实现

1. 定义"执行单元":业务的最小执行单位

先抽象出一个最简洁的接口,代表"一次业务执行":

java 复制代码
public interface Execution<R> {
    R execute(ExecutionContext context) throws Exception;
}

这个接口只包含业务关心的元素:有上下文(ExecutionContext)、有执行结果(R)、可能抛出异常。它不关心任何执行细节------同步还是异步、用哪个线程池、怎么调度,都和它没关系。

2. 统一"执行上下文":让信息在执行中流动

上下文是业务执行的"环境变量",我们用ThreadLocal实现上下文的跨线程传递:

java 复制代码
public class ExecutionContext {
    // 用 ThreadLocal 存储当前上下文
    private static final ThreadLocal<ExecutionContext> CONTEXT_HOLDER =
        ThreadLocal.withInitial(ExecutionContext::new);

    private String requestId;   // 链路跟踪用的请求ID
    private String operator;    // 操作人,用于审计
    private Map<String, Object> attributes = new HashMap<>();  // 业务参数

    /** 获取当前上下文 */
    public static ExecutionContext current() {
        return CONTEXT_HOLDER.get();
    }

    /** 设置上下文 */
    public static void set(ExecutionContext context) {
        CONTEXT_HOLDER.set(context);
    }

    /** 清理上下文 */
    public static void clear() {
        CONTEXT_HOLDER.remove();
    }

    // getter/setter 省略
}

上下文的实际使用流程

1. 请求入口设置上下文

在请求入口(如 Controller/Filter)统一初始化和设置上下文:

java 复制代码
@RestController
public class OrderController {

    @PostMapping("/order")
    public String createOrder(@RequestBody Order order) {
        // 1. 构建上下文
        ExecutionContext context = new ExecutionContext();
        context.setRequestId(UUID.randomUUID().toString()); // 生成或从请求头获取
        context.setOperator("currentUser"); // 从登录信息获取
        context.getAttributes().put("order", order);

        // 2. 绑定到当前线程
        ExecutionContext.set(context);

        try {
            // 3. 提交业务执行(同步/异步由引擎决定)
            ExecutionResult<Boolean> result = executorEngine.submit(ctx -> {
                Order orderFromCtx = ctx.getAttribute("order");
                orderService.createOrder(orderFromCtx);
                return true;
            });
            return "success";
        } finally {
            // 4. 请求结束清理上下文,避免内存泄漏
            ExecutionContext.clear();
        }
    }
}
3. 实现"执行引擎":统一的执行入口

有了执行单元和上下文,接下来需要一个统一的入口来执行它们:

java 复制代码
public interface ExecutorEngine {
    <R> ExecutionResult<R> submit(Execution<R> execution);
}

业务侧只需要提交执行单元,不需要关心执行方式。执行引擎会根据配置自动处理同步或异步。

同步执行引擎:最简单的实现,直接在当前线程执行

java 复制代码
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);
        }
    }
}

异步执行引擎:复用线程池,支持上下文传递

java 复制代码
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) {
        // 捕获当前线程上下文并传递到异步线程
        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 {
                // 清理上下文避免内存泄漏
                ExecutionContext.clear();
            }
        }, executor);
        return ExecutionResult.async(future);
    }
}
4. 业务侧的使用方式

业务只需要把原来的代码封装成执行单元,交给引擎处理:

java 复制代码
// 原来的同步/异步代码无需再维护两套
executorEngine.submit(ctx -> {
    Order order = ctx.getAttribute("order");
    orderService.createOrder(order);
    return true;
});

这段代码:

  • 不关心是同步还是异步执行;
  • 上下文自动传递;
  • 异常由引擎统一处理;
  • 日志、审计等通用逻辑在引擎层一次性处理,自动生效。

总结

通过统一执行模型的设计,我们可以将 业务逻辑执行方式清晰分离,这套模型在项目中能解决几个实际问题:

1. 代码可控 :不再到处是 @AsyncCompletableFuture,所有执行都有统一入口;

2. 上下文不丢:请求ID、操作人等信息在同步/异步中都能正确传递;

3. 扩展方便:要加重试、限流、熔断?在引擎层统一实现即可,业务代码无需修改;

4. 降低认知成本:新同学不需要刻意区分同步/异步方法,调用方式完全一致。

相关推荐
想摆烂的不会研究的研究生1 小时前
每日八股——Redis(1)
数据库·经验分享·redis·后端·缓存
毕设源码-郭学长1 小时前
【开题答辩全过程】以 基于SpringBoot技术的美妆销售系统为例,包含答辩的问题和答案
java·spring boot·后端
追逐时光者2 小时前
精选 10 款 .NET 开源免费、功能强大的 Windows 效率软件
后端·.net
追逐时光者2 小时前
一款开源、免费的 WPF 自定义控件集
后端·.net
S***q3773 小时前
Spring Boot管理用户数据
java·spring boot·后端
毕设源码-郭学长3 小时前
【开题答辩全过程】以 基于SpringBoot框架的民俗文化交流与交易平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
l***21783 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
f***14774 小时前
SpringBoot实战:高效实现API限流策略
java·spring boot·后端
计算机毕设VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue动物园管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计