修复 Tomcat 启动时的 OmniFaces BeanManager 初始化问题

fixing OmniFaces BeanManager initialization issues when Tomcat starts

我们最近迁移到 Tomcat 8.5.4(从 8.5.3)和 Omnifaces 2.4(从 2.3),我们还在 Web 应用程序中做了一些更改。从那时起,我们的 Web 应用程序不再启动,日志中出现以下异常:

Exception sending context initialized event to listener instance of class org.omnifaces.ApplicationListener

java.lang.ExceptionInInitializerError
at org.omnifaces.ApplicationListener.checkCDIAvailable(ApplicationListener.java:77)
at org.omnifaces.ApplicationListener.contextInitialized(ApplicationListener.java:61)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4716)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5178)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1403)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

Caused by: java.lang.IllegalStateException: CDI BeanManager instance is not available in JNDI.
at org.omnifaces.config.BeanManager.<init>(BeanManager.java:100)
at org.omnifaces.config.BeanManager.<clinit>(BeanManager.java:49)
... 11 more

Caused by: java.lang.IllegalStateException: javax.naming.NamingException: WELD-001300: Unable to locate BeanManager
at org.omnifaces.util.JNDI.lookup(JNDI.java:95)
at org.omnifaces.config.BeanManager.<init>(BeanManager.java:96)
... 12 more

Caused by: javax.naming.NamingException: WELD-001300: Unable to locate BeanManager
at org.jboss.weld.resources.ManagerObjectFactory.getObjectInstance(ManagerObjectFactory.java:62)
at org.apache.naming.factory.FactoryBase.getObjectInstance(FactoryBase.java:94)
at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:321)
at org.apache.naming.NamingContext.lookup(NamingContext.java:840)
at org.apache.naming.NamingContext.lookup(NamingContext.java:160)
at org.apache.naming.NamingContext.lookup(NamingContext.java:828)
at org.apache.naming.NamingContext.lookup(NamingContext.java:160)
at org.apache.naming.NamingContext.lookup(NamingContext.java:828)
at org.apache.naming.NamingContext.lookup(NamingContext.java:174)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:163)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at org.omnifaces.util.JNDI.lookup(JNDI.java:90)
... 13 more

Exception sending context initialized event to listener instance of class com.sun.faces.config.ConfigureListener

java.lang.RuntimeException: java.lang.NoClassDefFoundError: Could not initialize class org.omnifaces.config.BeanManager
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:292)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4714)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5178)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1403)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1393)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.omnifaces.config.BeanManager
at org.omnifaces.util.Beans.getManager(Beans.java:88)
at org.omnifaces.util.Beans.getReference(Beans.java:113)
at org.omnifaces.application.OmniApplication.<init>(OmniApplication.java:70)
at org.omnifaces.application.OmniApplicationFactory.createOmniApplication(OmniApplicationFactory.java:89)
at org.omnifaces.application.OmniApplicationFactory.getApplication(OmniApplicationFactory.java:54)
at com.sun.faces.application.InjectionApplicationFactory.getApplication(InjectionApplicationFactory.java:93)
at com.sun.faces.config.InitFacesContext.getApplication(InitFacesContext.java:142)
at com.sun.faces.lifecycle.ClientWindowFactoryImpl.<init>(ClientWindowFactoryImpl.java:62)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at javax.faces.FactoryFinderInstance.getImplGivenPreviousImpl(FactoryFinderInstance.java:405)
at javax.faces.FactoryFinderInstance.getImplementationInstance(FactoryFinderInstance.java:251)
at javax.faces.FactoryFinderInstance.getFactory(FactoryFinderInstance.java:543)
at javax.faces.FactoryFinder.getFactory(FactoryFinder.java:283)
at com.sun.faces.config.processor.FactoryConfigProcessor.verifyFactoriesExist(FactoryConfigProcessor.java:328)
at com.sun.faces.config.processor.FactoryConfigProcessor.process(FactoryConfigProcessor.java:236)
at com.sun.faces.config.ConfigManager.initialize(ConfigManager.java:439)
at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:227)

我们的配置:

BeanManager资源入口:

<Resource name="BeanManager"
          auth="Container"
          type="javax.enterprise.inject.spi.BeanManager"
          factory="org.jboss.weld.resources.ManagerObjectFactory"/>

我们已经回到 Tomcat 8.5.3 和 omnifaces 2.3,并在为了找出问题的根本原因,但无济于事。

最后,我们怀疑所有这些组件(Tomcat、Weld、Onmnifaces 等)的初始化之间存在竞争条件问题。

作为最后的手段,我从上下文 XML 文件中删除了 BeanManager 资源条目。

从上下文中删除:

<Resource name="BeanManager"
          auth="Container"
          type="javax.enterprise.inject.spi.BeanManager"
          factory="org.jboss.weld.resources.ManagerObjectFactory"/>

它解决了问题。

我查看了 Tomcat 8.5.4 的更改日志,我可以找到此更改:

Do not attempt to start web resources during a web application's initialisation phase since the web application is not fully configured at that point and the web resources may not be correctly configured. (markt)

我不知道 Tomcat 更改日志中报告的此 Web 应用程序的初始化阶段问题是否与我们遇到的问题有关。从上下文中删除 BeanManager 资源解决问题的原因仍然不清楚。

有什么想法吗?

javax.naming.NamingException: WELD-001300: Unable to locate BeanManager

这基本上意味着找到了 BeanManager JNDI 资源定义(如 context.xml 中所定义),但是尚未创建 JNDI 资源背后的具体 BeanManager 实例.

这确实匹配 Tomcat 8.5.4 中的 described change

Do not attempt to start web resources during a web application's initialisation phase since the web application is not fully configured at that point and the web resources may not be correctly configured. (markt)

我无法说出 Mark Thomas 做出此决定的原因,并且其背后也没有任何问题 link。我猜他是想避免因潜在的初始化顺序问题导致间歇性破坏部署而导致的混淆行为。这也许是一件好事,但我认为 Mark 实际上忽略了通过 web.xmlweb-fragment.xml 文件中的 <ordering> 元素定义初始化顺序的可能性。我确实记得 Tomcat 从来没有考虑过 @WebListener 注释或以编程方式创建的实例的调用顺序(但是它对 <listener> 声明的实例这样做)。也许 Mark 最好修复该部分,而不是在初始化阶段完全禁用 JNDI。

关于 OmniFaces 2.4 中的更改,根据 issue 243,此版本添加了对特定于 Weld 的 servlet 上下文属性的回退。这将在 JNDI 资源不存在且 Weld 被用作 CDI 实现时使用。也就是说,Weld 也在内部将 BeanManager 实例存储为 servlet 上下文属性,因此 OmniFaces 可以直接获取它而无需 JNDI。这意味着 context.xml 不再是必需的。在 Tomcat 8.5.4 中,您实际上也应该删除 context.xml,因为 OmniFaces 2.4 仍然首先尝试检查 JNDI 资源,这最终会抛出异常,因为 BeanManager 实例意外不可用作为 Tomcat 8.5.4 更改的结果。

在 OmniFaces 2.5 中,根据描述 CDI 1.0 到 1.1 迁移的 issue 281 对 CDI 初始化进行了重新设计和改进。 OmniFaces 不再使用 JNDI 检查 BeanManager。 OmniFaces 现在将使用引入的 CDI 1.1 CDI API.

BeanManager beanManager = CDI.current().getBeanManager();

这与 JNDI 无关 configuration/initialization。

OmniFaces 2.5 还不是最终版本,但 2.5-RC1 已于昨天在 Maven Central 中可用。