Java 将DB会话注入服务的调度程序的CDI作用域 上下文

Java 将DB会话注入服务的调度程序的CDI作用域 上下文,java,cdi,Java,Cdi,我正在开发一个web应用程序,其中我尝试集成运行在TomEE容器中的Vaadin、DB4O和CDI。为了拥有数据库事务,我创建了ServletFilter,它截取所有请求并在请求结束时提交或回滚 @WebFilter(“/*”) 公共类DBTransactionHandler实现过滤器{ @注入 SessionImpl SessionImpl; @凌驾 公共空间销毁(){ //TODO自动生成的方法存根 } @凌驾 public void doFilter(ServletRequest请求、Se

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

@WebFilter(“/*”)
公共类DBTransactionHandler实现过滤器{
@注入
SessionImpl SessionImpl;
@凌驾
公共空间销毁(){
//TODO自动生成的方法存根
}
@凌驾
public void doFilter(ServletRequest请求、ServletResponse响应、FilterChain链)抛出IOException,
ServletException{
布尔hadException=false;
试一试{
链式过滤器(请求、响应);
}捕获(RuntimeException ex){
hadException=true;
掷骰子;
}最后{
if(sessionImpl!=null){
if(hadException | | sessionImpl.isRollbackOnly()){
sessionImpl.rollback();
}否则{
提交();
}
sessionImpl.close();
}
}
}
@凌驾
public void init(FilterConfig arg0)抛出ServletException{
//TODO自动生成的方法存根
}
}
我有一个SessionImpl,它被设置为
@requestscope
。通过这种方式,我试图实现在处理HTTP请求时需要使用数据库的所有服务都应该获得相同的实例,因此它将在相同的数据库事务中执行

/**
* http://community.versant.com/documentation/reference/db4o-8.0/java/reference/Content/platform_specific_issues/web/servlets.htm
*/
@请求范围
公共类SessionImpl实现会话{
@注入
数据库连接工厂连接工厂;
私有布尔回滚;
私有对象容器委托;
@施工后
公共void init(){
委托=connectionFactory.getConnection().ext().openSession();
}
//…许多其他与数据库相关的方法
}
我的所有服务都是从
AbstractService
派生的,因此他们立即有一个工作会话

公共类抽象服务{
@注入
受保护的BeanManager BeanManager;
@注入
受保护会话数据库;
}
这就是我目前的情况,我的问题来了:

问题 在我的web应用程序中,我需要创建一个调度程序组件。计划的作业将使用与我已有的相同的服务。由于
SessionImpl
@requestscope
的,并且我在计划作业中没有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)

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

  • 使
    SessionImpl
    非CDI:删除
    @RequestScoped
    @Inject
  • 使用producer方法创建CDIBean,该方法创建会话,如下所示:

    // 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;
        }
    }
    
    到目前为止,您的web配置应该和以前一样工作。根据需要进行测试和调整。(请测试每个线程只生成一个会话。)

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

谢谢。这为解决问题指明了方向。经过更多的调整,我得到了一个有效的解决方案。很高兴听到这个消息。如果对你来说不太合适的话,你能为任何对未来感兴趣的人(包括我:-)发表这些观点吗?我之前没有说过我的调度程序组件使用CDI来发现可调度的作业。这是一个扩展Runnable的接口,添加了两个方法:getFirstRun(),getPeriod()。当我的调度程序找到一个可调度的时,它会创建一个实例,读取必须调度的时间和频率,然后将此实例传递给带有适当参数的ExecutorService。然后,当Executor运行此实例以及围绕此实例的包装器时(类似于上面的DBTransactionHandler)创建会话并将其放入ThreadLocal对我的可调度文件没有任何影响,因为对象已经构建,并且所有@Injections都已完成。这将注入到所有服务中,然后将所有与会话相关的请求委托给属于实际线程的SessionImpl。您可以在此处找到完整的源代码:
@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);
            }
        }
    }

    ...
}