4

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

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

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

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

  1. 某些低级操作(总是被调用)触发 CDI 事件
  2. 一个无状态的 EJB 监听这些事件,并在接收到一个事件后收集数据并将其存储到一个作用域 CDI bean 中(请求作用域或会话作用域都可以)
  3. 在请求结束时,另一个事件被触发,导致作用域 CDI bean 中的数据被处理

到目前为止,我们设法启动并运行了第 1 步和第 2 步。

但是,问题在于第 3 步:

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

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

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

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

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

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

我们想到了以下方法,但到目前为止都没有奏效:

  • 获取请求/对话范围的 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)

到目前为止,这有效,但仍然存在一个问题:

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

但是,当我们尝试调用 MBean 方法时,该方法又调用 EJB 并因此导致上述过程启动,我们得到一个ContextNotActiveException. 这表明在 JBoss 中执行 MBean 方法时请求上下文没有启动。

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

对此也有任何想法吗?

更新 2

好吧,看来我们现在可以运行它了。

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

通常我们应该能够对请求范围做同样的事情(至少如果我们没有错过规范中的任何内容)但是由于 JBoss 7.1 中有一个错误,当从 MBean 调用 EJB 或计划的作业(执行 JNDI 查找)。

在拦截器中,我们可以尝试获取一个活动的上下文,并在失败时激活 bean 管理器中存在的一个(很可能EjbRequestContext在这种情况下),但尽管我们进行了测试,但我们宁愿不要指望它在每种情况下都能正常工作。

然而,自定义作用域应该独立于任何 JBoss 作用域,因此不应该在这里干涉。

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

所以还有最后一个问题:我应该接受谁的回答,因为你们都帮助我们进入了正确的方向?- 我会尝试自己解决这个问题并将这些点归因于 jan - 他得到的最少:)

4

3 回答 3

4

使用带有注释的方法来完成这项工作@PreDestroy

@Named
@RequestScoped
public class Foo {

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

}

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

于 2012-06-19T17:02:55.490 回答
3

您正在寻找的是SessionSynchronization。这让 EJB 与事务生命周期相关联,并在事务完成时得到通知。

请注意,我是在具体讨论事务,您提到“请求和事务”,我不知道您是指 EJB 事务还是与您的应用程序相关的东西。

但我说的是 EJB 事务。

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

最后,在这些临时回调区域中要小心——在这些生命周期方法中,事务性发生了一些奇怪的事情。最后,最终将东西放入一个本地的、基于内存的队列中,另一个线程为提交到 JMS 或其他任何东西而获得了该队列。缺点是它们与手头的交易有关,优点是它们确实有效。

于 2012-06-19T18:55:38.507 回答
1

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

根据我对您到目前为止所尝试的了解,您在 CDI 技术方面非常先进 - 您没有什么大不了的。

我想说您应该能够在入口点激活对话上下文(您可能已经看过相关的文档?)并在整个处理过程中使用它。实际上可能值得考虑实现自己的范围。我曾经在一个遥远相关的场景中这样做过,我们无法判断我们是被 HTTP-request 还是 EJB-remoting 调用的。

但老实说,这一切都感觉太复杂了。这是一个相当脆弱的拦截器构造,它们通过事件相互通知,总而言之,这些事件似乎太容易被破坏。

是否有另一种更适合您需求的方法?例如,您可能会尝试连接事务管理本身并从那里执行您的数据积累?

于 2012-06-19T18:40:41.153 回答