调度程序的CDI范围,它将数据库会话注入服务

时间:2014-01-10 08:34:42

标签: java cdi

上下文

我正在开发一个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

    }

}

我有一个SessionImpl,它是@RequestScoped。有了这个,我试图在处理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 Context吗?

我尝试做的是创建自定义范围@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)

1 个答案:

答案 0 :(得分:1)

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

  • 制作SessionImpl非CDI:移除@RequestScoped@Inject
  • 使用生成Session s的生成器方法创建一个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中,生产者只是从那里得到它。谁把它放在那里?

  • 修改您的过滤器,将{em>并删除 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拦截器是可重用的,并且不会像这样使用系统逻辑来污染组件的业务逻辑。)