JavaEE:在请求/事务结束时执行任务

时间:2012-06-19 16:16:50

标签: java jboss ejb java-ee-6 cdi

我们希望在请求或事务结束时执行某些任务。更具体地说,我们需要在该请求期间收集一些数据,最后我们使用该数据进行一些自动数据库更新。

这个过程应尽可能透明,即需要这个过程的EJB用户不必担心这一点。

此外,我们无法控制确切的调用堆栈,因为该进程有多个入口点。

为了实现我们的目标,我们目前正在考虑以下概念:

  1. 某些低级别操作(总是被调用)会触发CDI事件
  2. 无状态EJB侦听这些事件,收到数据后,它会收集数据并将其存储到作用域的CDI bean中(请求范围或会话范围就可以了)
  3. 在请求结束时触发了另一个事件,导致处理范围内的CDI bean中的数据
  4. 到目前为止,我们设法启动并运行第1步和第2步。

    然而,问题是第3步:

    正如我已经说过的,该流程有多个入口点(源自Web请求,预定作业或远程调用),因此我们考虑了以下方法:

    3A。 CDI扩展扫描所有bean并为每个EJB添加注释。

    3B。为添加的注释注册了一个拦截器,因此在每次调用EJB方法时都会调用拦截器。

    3c上。第一次调用该拦截器将在调用的方法返回后触发事件。

    这就是问题所在(再次在第3步:) :):

    拦截器如何知道它是否是第一次调用?

    我们想到了以下内容,但到目前为止都没有工作:

    • 获取请求/会话范围的bean
      • 失败,因为没有上下文处于活动状态
    • 获取请求/会话上下文并激活它(然后应该标记第一次调用,因为在后续的上下文中应该是活动的)
      • 系统创建了另一个请求上下文,因此WELD最终得到至少两个活动请求上下文并抱怨此
      • 转换上下文保持活动状态或过早停用(我们还无法找出原因)
    • 启动一个长时间运行的对话并在调用后结束它
      • 失败,因为没有活动的请求上下文:(

    我们尚未尝试的另一种选择,但似乎不鼓励:

    然而,AFAIK并不能保证请求将完全由同一个线程处理,因此当容器决定切换到另一个线程时,甚至不会调用上下文传播?

    所以,感谢所有忍受我的人,并阅读所有冗长的描述。

    欢迎任何有关如何解决此问题的想法。

    顺便说一下,这里有一些我们正在使用的软件组件/标准(我们无法切换):

    • JBoss 7.1.0.Final(以及WELD和CDI 1.0)
    • EJB 3.1
    • Hibernate 3.6.9(暂时不能切换到4.0.0)

    更新

    根据您给出的建议,我们提出了以下解决方案:

    1. 使用请求范围对象将数据存储在
    2. 第一次将对象存储在该对象中时触发了一个事件
    3. 在事务结束前调用侦听器(使用@Observes(during=BEFORE_COMPLETION) - 谢谢,@ bkail)
    4. 到目前为止这仍然有效,但仍有一个问题:

      我们还有由CDI管理并自动注册到MBean服务器的MBean。因此,那些MBean可以注入EJB引用。

      但是,当我们尝试调用MBean方法,而MBean方法又调用EJB并因此导致上述过程开始时,我们得到ContextNotActiveException。这表明在JBoss中,执行MBean方法时不会启动请求上下文。

      使用JNDI查找来获取服务而不是DI时,这也不起作用。

      关于这一点的任何想法?

      更新2

      好吧,好像我们现在正在运行它。

      基本上我们做了我在上一次更新中所描述的内容,并通过创建我们自己的范围和上下文(在第一次调用EJB方法时激活,并在相应的拦截器完成时停用)解决了上下文不活动的问题。

      通常我们应该能够对请求范围做同样的事情(至少如果我们没有错过规范中的任何内容)但是因为JBoss 7.1中存在一个错误,所以当时没有一个活动的请求上下文从MBean或预定作业(执行JNDI查找)调用EJB。

      在拦截器中,我们可以尝试获取活动上下文并在失败时激活bean管理器中存在的那些(在这种情况下很可能是EjbRequestContext)但是尽管我们进行了测试,但我们还是不计算在每种情况下工作。

      然而,自定义范围应独立于任何JBoss范围,因此不应干扰此处。

      感谢所有回答/评论的人。

      所以,这是最后一个问题:我们应该接受哪些答案,因为你们都帮助我们走上了正确的方向? - 我会尝试自己解决这个问题并将这些观点归结为jan - 他得到的最少:)

3 个答案:

答案 0 :(得分:4)

在使用@PreDestroy注释的方法中完成工作。

@Named
@RequestScoped
public class Foo {

    @PreDestroy
    public void requestDestroyed() {
        // Here.
    }

}

在容器销毁bean实例之前调用它。

答案 1 :(得分:2)

您正在寻找的是SessionSynchronization。这使得EJB可以与事务生命周期联系起来,并在事务完成时得到通知。

注意,我是关于事务的具体内容,你提到“请求和事务”,我不知道你是否具体指EJB事务或与你的应用程序绑定的东西。

但我在谈论EJB Transactions。

缺点是它只在调用特定EJB时调用,而不是在一般情况下调用“所有”事务。但无论如何,这可能是合适的。

最后,在这些临时回调区域要小心 - 在这些生命周期方法中,事务发生时我发生了奇怪的事情。最后,最终将内容放入一个本地的,基于内存的队列,另一个线程收集到JMS或其他任何东西。缺点是它们与手头的交易挂钩,其中的好处是它们确实有效。

答案 2 :(得分:1)

Phew,这是一个复杂的场景:)

从我如何理解你到目前为止所尝试的内容来看,你对CDI技术非常了解 - 你没有什么大不了的。

我会说你应该能够在入口点激活对话上下文(你可能已经看过the relevant documentaton?)并在整个处理过程中使用它。考虑实施自己的范围实际上是值得的。我曾经在远程相关的场景中做过一次,我们无法判断我们是否已经被HTTP请求或EJB-remoting调用。

但说实话,这一切都太复杂了。这是一个相当脆弱的拦截器构造,通过事件来互相通知,这些事件似乎都很容易打破。

还有其他方法可以更好地满足您的需求吗?例如。您可能尝试挂钩事务管理本身并从那里执行数据累积?