【问题标题】:Java injection inside @Asynchronous bean@Asynchronous bean 中的 Java 注入
【发布时间】:2016-12-17 16:47:24
【问题描述】:

我有 2 个 bean 使用注入来“传递”从 HttpRequest 提取的 UserData 信息。如果我从 WorkerBean 中删除 @Asynchronous 那么它的所有工作和 WorkerBean 可以访问被注入的 UserInfo。 但是,如果我在 WorkerBean 上使用@Asynchronous,则注入将停止工作。

如果必须是异步的,手动创建/传递 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
    }
}

【问题讨论】:

  • 我认为你正在寻找的是不可行的,因为 WorkerBean 将在应用程序启动时初始化,而 UserData 将在获取请求时初始化,所以在创建 WorkerBean 时没有 UserData .
  • 如果我可以手动创建 UserData 并将其填充到 MainRs 中。有没有办法在 WorkerBean 中手动注入这个 UserData?
  • 您遇到的另一个问题是 UserData 中嵌入了 HttpServletRequest。您无法保证您的服务器实现不会重复使用该请求来服务另一个客户端。这引发了安全问题
  • 正如@SteveC 提到的,您需要 HttpServletRequest 实例来创建 UserData,但是如果您想手动注入某些东西,则必须公开 getter 和 setter 并使用它们来设置您想要的值。
  • 那么,如果我将 HttpRequest 传递给异步 bean,我该如何手动重建我的 UserData?

标签: java asynchronous ejb inject


【解决方案1】:

UserData 是 RequestScoped 并绑定到 http 请求上下文,这意味着它依赖于当前请求,因此依赖于当前执行线程。 @Asynchronous 将主要通过使用服务器的线程池来实现。此外,CDI 不会将上下文传播到另一个线程,至少对于会话和请求上下文。在这种情况下,它会创建一个新的 request-context,一个 ejb-invocation 请求上下文,它与 http-request-context 无关。因此,在另一个线程中,您丢失了所有 http 会话和 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();
  }

}

关于 managedexecutorservice 中的线程局部变量的一些注意事项。线程是 重用,您必须确定您传播的上下文是 删除,否则在具有不同用户数据的不同会话中,您 会得到混淆的数据,而且这将是一个难以调试的场景。

如果您可以避免这种情况,并且只需将UserData 作为方法参数传递,那就更好了。

【讨论】:

  • 不幸的是我必须使用@Asynchronous。我很惊讶没有办法手动将对象插入/销毁到上下文中
猜你喜欢
  • 1970-01-01
  • 2011-12-05
  • 2016-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-14
  • 1970-01-01
相关资源
最近更新 更多