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
相关产品推荐
相关产品推荐

