【问题标题】:CDI Scope for Scheduler which inject DB Session into Services将数据库会话注入服务的调度程序的 CDI 范围
【发布时间】:2014-01-10 08:34:42
【问题描述】:

背景

我正在开发一个 Web 应用程序,我尝试在其中集成 Vaadin 和 DB4O 以及在 TomEE 容器中运行的 CDI。为了拥有数据库事务,我创建了 ServletFilter,它拦截所有请求并在请求结束时提交或回滚。

@WebFilter("/*")
public class DBTransactionHandler implements Filter {

    @Inject
    SessionImpl sessionImpl;

    @Override
    public void destroy() {
            // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
     ServletException {
            boolean hadException = false;
            try {
                    chain.doFilter(request, response);
            } catch (RuntimeException ex) {
                    hadException = true;
                    throw ex;
            } finally {
                    if (sessionImpl != null) {
                            if (hadException || sessionImpl.isRollbackOnly()) {
                                    sessionImpl.rollback();
                            } else {
                                    sessionImpl.commit();
                            }
                            sessionImpl.close();
                    }
            }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
            // TODO Auto-generated method stub

    }

}

我有一个设置为@RequestScoped 的SessionImpl。有了这个,我试图实现在处理 HTTP 请求时需要使用数据库的所有服务,然后它们应该获得相同的实例,因此它将在相同的数据库事务中执行。

/**
* http://community.versant.com/documentation/reference/db4o-8.0/java/reference/Content/platform_specific_issues/web/servlets.htm
*/
@RequestScoped
public class SessionImpl implements Session {

    @Inject
    DBConnectionFactory connectionFactory;

    private boolean rollbackOnly;

    private ObjectContainer delegate;

    @PostConstruct
    public void init() {
            delegate = connectionFactory.getConnection().ext().openSession();
    }
    //.... many other database related methods
}

我的所有服务都来自AbstractService,因此他们立即进行了工作会话。

public class AbstractService {

    @Inject
    protected BeanManager beanManager;

    @Inject
    protected Session db;

}

这就是我目前所拥有的,我的问题来了:

问题

在我的 Web 应用程序中,我需要创建一个 Scheduler 组件。预定的作业将使用我已经拥有的相同服务。由于SessionImpl@RequestScoped 并且我在计划的作业中没有HTTP 请求,因此无法注入SessionImpl。

  • 我能否以某种方式从调度程序线程激活 RequestScope 上下文?

我试图做的是创建一个自定义范围@SchedulerScoped。这将在调度程序开始执行作业之前激活。这种方法的问题在于,当我向SessionImpl 添加第二个范围时,我的应用程序不再部署:

SEVERE: CDI Beans module deployment failed
org.apache.webbeans.exception.WebBeansConfigurationException: Managed Bean implementation class : org.reluxa.db.SessionImplstereotypes must declare the same @Scope annotations.
    at org.apache.webbeans.config.DefinitionUtil.defineScopeType(DefinitionUtil.java:390)
    at org.apache.webbeans.component.creation.AbstractBeanCreator.defineScopeType(AbstractBeanCreator.java:145)
    at org.apache.webbeans.util.WebBeansUtil.defineManagedBean(WebBeansUtil.java:2548)
    at org.apache.openejb.cdi.BeansDeployer.defineManagedBean(BeansDeployer.java:552)
    at org.apache.openejb.cdi.OpenEJBLifecycle.deployManagedBeans(OpenEJBLifecycle.java:407)

【问题讨论】:

    标签: java cdi


    【解决方案1】:

    这是一个有趣的问题。我将提出以下解决方案的大纲我实际上没有尝试过,因此需要进行一些调整:

    • 使SessionImpl 成为非CDI:删除@RequestScoped@Inject
    • 使用创建 Sessions 的生产者方法制作 CDI bean,如下所示:

      // I believe it should be @ApplicationScoped
      public class SessionProducer {
          private ThreadLocal<Session> currentSession;
          @Produces Session makeSession() {
              return currentSession.get();
          }
          public ThreadLocal<Session> getCurrentSessionThreadLocal() {
              return currentSession;
          }
      }
      

      所以会话驻留在ThreadLocal 中,而生产者只是从那里获取它。谁把它放在那里的?

    • 修改您的过滤器,将Session 放入并删除ThreadLocal

      @WebFilter("/*")
      public class DBTransactionHandler implements Filter {
      
          @Inject
          DBConnectionFactory connectionFactory;
      
          @Inject
          SessionProducer sessionProducer;
      
          ...
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              boolean hadException = false;
              try {
                  SessionImpl sessionImpl = new SessionImpl();
                  sessionImpl.setConnectionFactory(connectionFactory);
                  sessionProducer.getCurrentSessionThreadLocal().set(sessionImpl);
                  // here you probably want to call sessionImpl.init();
                  chain.doFilter(request, response);
              } catch (RuntimeException ex) {
                  hadException = true;
                  throw ex;
              } finally {
                  if (sessionImpl != null) {
                      if (hadException || sessionImpl.isRollbackOnly()) {
                          sessionImpl.rollback();
                      } else {
                          sessionImpl.commit();
                      }
                      sessionImpl.close();
                      sessionProducer.getCurrentSessionThreadLocal().set(null);
                  }
              }
          }
      
          ...
      }
      

      到这里为止,您的网络配置应该像以前一样工作。根据需要进行测试和调整。 (请测试每个线程只产生一个会话。)

    • 现在您必须在调度程序中重复SessionImpl 创建和销毁仪式。这样服务仍然会看到一个有效的Session,而不需要请求范围。这种“仪式”可能会被排除在外(例如,执行这些操作的调度程序的 CDI 或 EJB 拦截器是可重用的,并且不会像这样的系统逻辑污染组件的业务逻辑)。

    【讨论】:

    • 谢谢。这指出了解决方案。通过更多的调整,我得到了一个可行的解决方案。
    • 很高兴听到。如果对您来说不是很多,您能否将这些观点发布给任何可能对未来感兴趣的人(包括我在内:-)?
    • 我之前没有告诉过我的调度程序组件使用 CDI 来发现可调度作业。这是一个扩展 Runnable 的接口,添加了两个方法:getFirstRun()、getPeriod()。当我的调度程序找到一个 Schedulable 时,它​​会创建一个实例,它会在必须调度时读出时间和频率,然后使用适当的参数将此实例传递给 ExecutorService。
    • 然后当 Executor 运行 this 并且围绕它的包装器(类似于上面的 DBTransactionHandler)创建 Session 并将其放入 ThreadLocal 对我的 Schedulable 没有影响,因为对象已经构造,所有的@Injections 都完成了。
    • 所以我创建了一个 SessionThreadLocalWrapper (pastebin.com/ybqLe2sV)。这注入到所有服务中,然后将所有与会话相关的请求委托给属于实际线程的 SessionImpl。你可以在这里找到完整的源代码:github.com/reluxa/VDC
    猜你喜欢
    • 1970-01-01
    • 2012-01-20
    • 2022-10-24
    • 2020-01-14
    • 1970-01-01
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多