包含的启用 CDI 的 Servlet 失败并出现 ContextNotActiveException:WELD-001303:

Sco*_*ous 3 java tomcat servlets cdi weld

我正在编写一个在 Tomcat 上运行的 CDI 应用程序。我使用 Tomcat 7.0.62 和 Weld 2.2.12.Final 作为 CDI 实现。我正在使用 CDI,而不使用 JSF。

该应用程序由未启用 CDI 的调度程序 servlet 组成。调度程序包括启用 CDI 的 servlet 的输出来创建页面。

当调度程序 servlet 和 CDI servlet 都在同一个 Web 应用程序中时,它可以正常工作。但是,我需要 CDI servlet 位于不同的 Web 应用程序中,因此我执行了跨上下文包含。当我执行跨上下文包含时,CDI servlet 会生成输出,直到它尝试访问 @RequestScoped bean。Bean 访问失败,出现以下异常:

org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
    at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:708)
    at org.jboss.weld.bean.ContextualInstanceStrategy$DefaultContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:90)
    at org.jboss.weld.bean.ContextualInstanceStrategy$CachingContextualInstanceStrategy.getIfExists(ContextualInstanceStrategy.java:165)
    at org.jboss.weld.bean.ContextualInstance.getIfExists(ContextualInstance.java:63)
    at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:83)
...
Run Code Online (Sandbox Code Playgroud)

我也尝试过为调度程序 servlet 激活 CDI,但似乎没有任何区别。

在我看来,当包含 CDI servlet 时,CDI servlet 的请求上下文没有正确设置,而不是直接接收请求。

我已经搜索过这个网站,也通过谷歌搜索过,但没有找到匹配的问题/解决方案。我发现一个 tomcat 上下文设置“fireRequestListenersOnForwards=”true”,我将其应用于调度程序 servlet,但这并没有什么区别。

这是配置问题吗?任何人都可以提供有关如何解决这个问题的线索吗?

我会很感激!


背景资料:

存在问题的实际应用程序很大,因此我将其浓缩以了解损坏的本质。结果,我有两个战争文件。第一个 war 文件包含 CDI servlet 和调度程序(在代码中我将其称为包含程序)servlet。第二个 war 文件仅包含调度程序 servlet。

CDI Servlet

CDI Servlet 的 META_INF 目录中有一个 context.xml 文件,其中包含以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Resource name="BeanManager" 
        auth="Container"
        type="javax.enterprise.inject.spi.BeanManager"
        factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>
Run Code Online (Sandbox Code Playgroud)

CDI servlet web.xml 文件包含以下行:

<listener>
  <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>

<resource-env-ref>
    <resource-env-ref-name>BeanManager</resource-env-ref-name>
    <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
Run Code Online (Sandbox Code Playgroud)

CDI Servlet WEB-INF 目录包含 beans.xml 文件。

CDI servlet 通过使用通过 JNDI 查找获得的 BeanManager 来引导 bean 执行(这是有效的):

BeanManager bm = null;
try {
    InitialContext context = new InitialContext();

    try {
       // "regular" naming
       bm = (BeanManager) context.lookup("java:comp/BeanManager");
    } catch(NameNotFoundException e) {
       // try again with Tomcat naming
       bm = (BeanManager) context.lookup("java:comp/env/BeanManager");
    }
} catch (Exception e) {}

if (bm == null) {
   writer.write("Couldn't look up the bean manager");
} else {
   Set<Bean<?>> beans = bm.getBeans(EnclosingBean.class);
   @SuppressWarnings("unchecked")
   Bean<EnclosingBean> bean = (Bean<EnclosingBean>) bm.resolve(beans);
   if (bean == null) {
      writer.write("Couldn't get the bean");
   } else {
      EnclosingBean eb = (EnclosingBean) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
      writer.write("finally here we are. Name is: ");
      writer.write(eb.getName());
   }
}
Run Code Online (Sandbox Code Playgroud)

包含器(调度程序)Servlet

包含器 Servlet 在其 META-INF 目录中有一个 context.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<Context 
   path="/ExternalIncluderServlet" 
   docBase="ExternalIncluderServlet.war" 
   crossContext="true" 
   fireRequestListenersOnForwards="true">

    <Resource name="BeanManager" 
        auth="Container"
        type="javax.enterprise.inject.spi.BeanManager"
        factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>
Run Code Online (Sandbox Code Playgroud)

包含器 servlet 查找 CDI servlet 的上下文并获取 RequestDispatcher,如下所示(这是有效的):

ServletContext sc = request.getServletContext();
ServletContext extsc = sc.getContext("/SimpleCDIServlet");
if (extsc == null) {
   writer.println("<p>Couldn't get the external context.</p>");
} else {

   RequestDispatcher rd = extsc.getRequestDispatcher("/CDIServlet");
   if (rd == null) {
        writer.println("<p>RequestDispatcher is null.</p>");
   } else {
       writer.println("<p>Got the RequestDispatcher.</p>");
       rd.include(req, resp);
   }
}
Run Code Online (Sandbox Code Playgroud)

结果:

当我使用浏览器直接通过 URI 访问 CDI servlet 时: localhost:8080/SimpleCDIServlet/CDIServlet 我得到了预期的输出:

Simple CDI Servlet
finally here we are. Name is: InjectedBean
Run Code Online (Sandbox Code Playgroud)

如果我通过与 CDI Servlet 位于同一 Web 应用程序中的调度程序 Servlet 访问 CDI Servlet,它也能正常工作。URI:/SimpleCDIServlet/IncluderServlet,输出:

Simple CDI Servlet Including Servlet
Will now include the CDI servlet ...
Got the RequestDispatcher.
Simple CDI Servlet
finally here we are. Name is: InjectedBean
Run Code Online (Sandbox Code Playgroud)

但是,如果我包含来自不同上下文的 CDI servlet,我不会在输出中获得注入的 bean 名称,并且上面提到的异常会出现在日志中。URI:/ExternalInincluderServlet/IncluderServlet,输出:

CDI Servlet Includer

This servlet includes a CDI servlet in a different web app. It is not CDI enabled.

Got the RequestDispatcher.
Simple CDI Servlet
finally here we are. Name is: 
Run Code Online (Sandbox Code Playgroud)

请注意,我无法使用转发请求调度程序来代替 include,因为原始应用程序包含来自多个其他 servlet(而不仅仅是一个)的输出。并且为每个包含打开一个新的 HTTP 请求效率很低,因为请求的数量将乘以包含的 servlet 的数量,而且除此之外会相当难看。


更新: 我在 Tomee 1.7.2 和 WebSphere Application Server v8.5 上进行了尝试。结果总结如下。

                           WAS 8.5 Tomcat 7.0.62 Tomee 1.7.2
                           ======= ============= ===========
CDI servlet 直接访问 有效 有效 有效

包含在 servlet 中 作品 作品 作品          
   在同一个网络应用程序中

servlet 包含的作品坏坏了
   在不同的网络应用程序中

我越想这个,就越觉得它确实应该有效。您应该能够成功使用请求调度程序来包含 Tomcat 上启用 CDI 的 servlet 的输出。我希望这里有人能帮助我弄清楚如何让它发挥作用。

Sco*_*ous 6

哇!在好朋友的帮助下,我搞定了!

要实现此功能,您需要配置一个特殊的 Weld 跨上下文过滤器。您可以通过将以下行添加到 web.xml 文件来配置过滤器。

<filter>
   <filter-name>WeldCrossContextFilter</filter-name>
   <filter-class>org.jboss.weld.servlet.WeldCrossContextFilter</filter-class>
</filter>
<filter-mapping>
   <filter-name>WeldCrossContextFilter</filter-name>
   <url-pattern>/*</url-pattern>
   <dispatcher>INCLUDE</dispatcher>
   <dispatcher>FORWARD</dispatcher>
   <dispatcher>ERROR</dispatcher>
</filter-mapping>
Run Code Online (Sandbox Code Playgroud)

当我配置此选项时,CDI servlet 的跨上下文包含将按预期工作。我在 Tomcat 7.0.42 和 8.0.23 上尝试过,到目前为止,效果很好。

也可以看看:

http://javadox.com/org.jboss.weld/weld-core-impl/2.2.4.Final/org/jboss/weld/servlet/WeldCrossContextFilter.html

我在官方 Weld 文档中查找了对过滤器的引用,但找不到任何内容。无论如何,我希望这可以帮助某人。