我有 2 个 bean 使用注入(inject)来“传递”从 HttpRequest
中提取的 UserData 信息。如果我从 WorkerBean 中删除 @Asynchronous
,那么它的所有工作和 WorkerBean 都可以访问注入(inject)的 UserInfo。
但是,如果我在 WorkerBean 上使用 @Asynchronous
,则注入(inject)将停止工作。
如果必须异步,手动创建 UserInfo 或将其传递到 WorkerBean 的最佳方法是什么?
// resource class
@Stateless
class MainRs {
@Context
protected HttpServletRequest request;
@Inject
protected UserData userData;
@EJB
WorkerBean job;
@Path("/batch/job1")
public function startJob() {
// call on worker bean
job.execute();
}
}
// user data extracted from HttpRequest
@RequestScoped
@Default
class UserData {
private HttpServletRequest request;
private String userId;
@Inject
public UserData(HttpServletRequest request) {
super();
this.request = request;
userId = request.getHeader("userId");
}
public int getUserId() {
return userId;
}
}
@Stateless
@Asynchronous
class WorkerBean {
private UserData userData;
// inject userData rom caller bean
@Inject
public WorkerBean(UserData userData) {
super();
this.userData = userData;
}
public function execute() {
String userId = userData.getUserId();
// do something
}
}
最佳答案
UserData 是 RequestScoped
并且绑定(bind)到 http 请求上下文,这意味着它依赖于当前请求,因此依赖于当前执行线程。 @Asynchronous
将主要通过使用服务器的线程池来实现。此外,CDI 不会将上下文传播到另一个线程,至少对于 session 和请求上下文而言。在这种情况下,它创建了一个新的请求上下文,一个 ejb-invocation 请求上下文,它与 http-request-context 无关。因此,在不同的线程中,您丢失了所有 http session 和 http 请求上下文数据。按照当前的 CDI 规范,没有办法解决它。
我的工作涉及完全放弃 @Asynchronous
注释,并使用 ManagedExecutionService
,它基于某些标准,对于我需要的一些数据,传播一些上下文数据到线程,通过 ThreadLocal
。因此:
@Stateless
public class AsynchronouseService {
@Resource
private ManagedExecutorService managedExecutorService;
@EJB
private AsynchronouseServiceDelegate asynchronousServiceDelegate;
@Inject
private ManagedContextData managedContextData;
public void executeAsync(Runnable runnable) {
managedExecutorService.submit(() -> asynchronousServiceDelegate.execute(runnable, managedContextData));
}
}
@Stateless
public class AsynchronouseServiceDelegate {
@Inject
private ManagedContextDataProvider managedContextDataProvider;
public void execute(Runnable runnable, ManagedContextData managedContextData){
try {
managedContextDataProvider.setExecutionContextData(managedContextData)
runnable.run();
} finally {
managedContextDataProvider.clearExecutionContextData();
}
}
}
```
@ApplicationScoped
public class ManagedContextDataProvider {
private static final ThreadLocal<ManagedContextData> managedContextDataContext;
@Inject
private Instance<HttpSession> httpSession;
@Produces
@Depedent
public ManagedContextData getManagedContextData() {
firstNonNull(managedContextDataContext.get(), httpSession.get().getAttribute(context_data_key));
}
public void setExecutionContextData(ManagedContextData managedContextData) {
managedContextDataContext.set(managedContextData);
}
public void clearExecutionContextData() {
managedContextDataContext.remove();
}
}
Some NOTE about threadlocals in managedexecutorservice. Threads are reused, you must be certain that the context you propagate are removed, otherwise on a different session with different userdata, you will get mixed-up data, and it will be a hard-to-debug scenario.
如果你能避免这种情况,并且只将 UserData
作为方法参数传递,那就更好了。
关于@Asynchronous bean 中的 Java 注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38888007/