【问题标题】:Keep @RequestScoped context when receiving an async CDI event接收异步 CDI 事件时保留 @RequestScoped 上下文
【发布时间】:2022-10-12 23:25:23
【问题描述】:
我想从同步触发 CDI bean 切换到异步,以便能够并行工作。
event.fire(myObject) -> event.fireAsync(myObject)
由于我目前使用请求上下文来了解当前进程是关于哪个租户的,所以我遇到了一个问题,@RequestScoped 上下文在 @ObservesAsync 方法中丢失了。因此,我不再知道要保留什么数据库等。我可以在 cdi 事件对象中提供必要的信息,并在接收后手动重新创建请求上下文,但这会使我的对象膨胀并使我的代码混乱。
有没有办法简单地保留异步 cdi 事件的请求上下文?
【问题讨论】:
标签:
asynchronous
jakarta-ee
scope
cdi
quarkus
【解决方案1】:
请求范围的对象不需要是线程安全的,通常不是。因此,请求上下文永远不会跨线程自动传播。对于异步事件,确实应该将所有必要的数据放入事件对象中。
你当然不是第一个问这个问题的人。已经尝试为上下文传播(MicroProfile Context Propagation,Jakarta Concurrency)定义一个 API/SPI,包括 CDI 请求上下文,但它们仅在带有线程跳转的顺序处理的情况下才能正常工作(在非阻塞/反应式编程中很常见)。如果您尝试 [ab] 使用上下文传播进行并发处理,那么您将遇到麻烦。有关此问题的最新讨论,请参阅https://github.com/jakartaee/cdi/issues/474
【解决方案2】:
我实际上切换到使用接口。这给了我更多的控制权并使代码更容易理解:
abstract class Publisher<T>{
@All
@Inject
private List<EventConsumer<T>> eventConsumers;
@Inject
private ContextInfo contextInfo;
@Inject
private MutableContextInfo mutableContextInfo;
...
public void publishEvent(T event){
String myContextInfo= contextInfo.getMyContextInfo();
eventConsumers.forEach(consumer -> notifyAsync(consumer, receivedObject, myContextInfo))
}
private void notifyAsync(EventConsumer<T> consumer, T object, String myContextInfo) {
Uni.createFrom()
.voidItem()
.subscribeAsCompletionStage()
.thenAccept(voidItem -> notifyConsumer(consumer, object, myContextInfo));
}
/**
* Method needs to be public to be able to activate request context on self invocation
*/
@ActivateRequestContext
public void notifyConsumer(EventConsumer<T> consumer, T object, String myContextInfo) {
mutableContextInfo.setMyContextInfo(myContextInfo);
try {
consumer.onEvent(object);
} catch (RuntimeException ex) {
log.error("Error while promoting object to eventconsumer", ex);
}
}
}