You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中避免将CompletableFuture作为EventListener共享状态的方案

无状态实现异步@EventListener响应的方案

方案一:通过请求ID关联CompletableFuture(适用于事件触发逻辑与请求解耦的场景)

使用线程安全的映射存储请求唯一标识与对应CompletableFuture的关联,Bean本身不持有请求级别的状态:

@Service
public class AsyncHandler {
    // 线程安全的Map,临时存储请求ID与CompletableFuture的映射
    private final ConcurrentHashMap<String, CompletableFuture<My>> futureMap = new ConcurrentHashMap<>();

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Async
    public CompletableFuture<My> getMy() {
        String requestId = UUID.randomUUID().toString();
        CompletableFuture<My> future = new CompletableFuture<>();
        futureMap.put(requestId, future);

        // 发布携带请求ID的事件,触发后续业务逻辑生成My对象
        eventPublisher.publishEvent(new MyEvent(this, requestId));

        // 添加超时清理,避免内存泄漏
        CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS).execute(() -> {
            if (!future.isDone()) {
                future.completeExceptionally(new TimeoutException("等待MyEvent超时"));
                futureMap.remove(requestId);
            }
        });

        return future;
    }

    @EventListener
    public void processEvent(MyEvent event) {
        String requestId = event.getRequestId();
        CompletableFuture<My> future = futureMap.remove(requestId);
        if (future != null && !future.isDone()) {
            future.complete(event.getMy());
        }
    }
}

// 改造MyEvent,添加请求ID字段
public class MyEvent extends ApplicationEvent {
    private final String requestId;
    private My my;

    public MyEvent(Object source, String requestId) {
        super(source);
        this.requestId = requestId;
    }

    public String getRequestId() {
        return requestId;
    }

    public void setMy(My my) {
        this.my = my;
    }

    public My getMy() {
        return my;
    }
}

方案说明

  • 每个getMy()调用生成唯一requestId,将CompletableFuture存入线程安全的ConcurrentHashMap,同时发布带requestId的事件。
  • 事件监听器通过requestId从Map中取出对应Future并完成,完成后立即移除Map中的条目,避免内存占用。
  • 添加超时机制,防止因事件未触发导致的内存泄漏。
  • Bean本身无状态,所有请求级状态都存储在Map的临时条目里,符合Spring单例Bean的无状态规范。

方案二:直接在事件中携带CompletableFuture(适用于事件触发逻辑与请求强关联的场景)

如果事件的触发逻辑与getMy()调用在同一路径下,可以直接将CompletableFuture作为事件的一部分传递,完全不需要额外存储:

@Service
public class AsyncHandler {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Async
    public CompletableFuture<My> getMy() {
        CompletableFuture<My> future = new CompletableFuture<>();
        // 发布事件时直接传入Future
        eventPublisher.publishEvent(new MyEvent(this, future));

        // 同样添加超时保护
        CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS).execute(() -> {
            if (!future.isDone()) {
                future.completeExceptionally(new TimeoutException("等待MyEvent超时"));
            }
        });

        return future;
    }

    @EventListener
    public void processEvent(MyEvent event) {
        CompletableFuture<My> future = event.getFuture();
        if (!future.isDone()) {
            future.complete(event.getMy());
        }
    }
}

// 改造MyEvent,添加CompletableFuture字段
public class MyEvent extends ApplicationEvent {
    private final CompletableFuture<My> future;
    private My my;

    public MyEvent(Object source, CompletableFuture<My> future) {
        super(source);
        this.future = future;
    }

    public CompletableFuture<My> getFuture() {
        return future;
    }

    public void setMy(My my) {
        this.my = my;
    }

    public My getMy() {
        return my;
    }
}

方案说明

  • 无需任何外部存储,直接通过事件传递CompletableFuture,实现请求与事件的关联。
  • Bean完全无状态,没有任何实例变量持有请求相关数据,是最简洁的无状态实现方式。
  • 同样需要添加超时机制,避免Future长期占用内存。

内容的提问来源于stack exchange,提问作者JiKra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 01:45:30