问题
I have 2 beans that use Injection to "pass" UserData info that is extracted from HttpRequest
. If I remove @Asynchronous
from WorkerBean then its all working and WorkerBean can access UserInfo thats injected down.
However if I use @Asynchronous
on WorkerBean then injection stops working.
What is the best way to manually create/pass UserInfo into WorkerBean if it has to be asynchronous?
// 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
}
}
回答1:
UserData is RequestScoped
and bounded to the http request context, that implies that it is dependent on the current request, and hence on the current thread of execution. @Asynchronous
would be implemented, mostly through the use of the server's thread-pool. Additional, CDI does not propagate contexts to another thread, at least for session and request context. In this case, it creates a new request-context, an ejb-invocation request context, which has nothing to do with the http-request-context. In a different thread therefore, you have lost all http session and http request context data. As at current CDI specs, there is no way around it.
My work around involved ditching the @Asynchronous
annotation altogether, and using ManagedExecutionService
, which based on certain criteria's, and for some data that I require, propagates some contexts data to the thread, through ThreadLocal
. Thus:
@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.
If you can avoid this scenario, and just pass through the UserData
as method parameter, the better.
来源:https://stackoverflow.com/questions/38888007/java-injection-inside-asynchronous-bean