MyFaces应用从WebLogic迁移至Tomcat时initFaces空指针异常
问题背景
- 迁移目标:将老旧MyFaces 2.0 Web应用从WebLogic Server 12.1.3迁移至Tomcat 9,搭配OpenWebBeans作为CDI实现
- 初始问题:仅引入
cdi-api-1.0.jar无法实现CDI能力集成,参照公开的Tomcat集成OpenWebBeans指南完成配置后,应用启动及访问阶段持续抛出异常
已完成的排查步骤
- 初始版本(MyFaces 2.0.0 + OpenWebBeans 2.0.20):启动阶段MyFaces initFaces环节抛出
java.lang.NullPointerException,异常触发点为org.apache.myfaces.application.DefaultResourceHandlerSupport.calculateFacesServletMapping(DefaultResourceHandlerSupport.java:211),反编译jar包后定位到疑似externalContext.getRequestServletPath()返回null,已在web.xml中为FacesServlet配置*.xhtml扩展名映射,未确认是否为根因 - 第一次升级(MyFaces 2.0.25 + OpenWebBeans 2.0.27):异常触发点变更为
org.apache.webbeans.container.BeanManagerImpl.wrapExpressionFactory(BeanManagerImpl.java:1288),仍为空指针异常 - 第二次升级(MyFaces 2.3.10):在
web.xml中补充配置StartupServletContextListener监听器后,上述空指针异常未消除,触发点与上一版本一致 - 依赖调整阶段:参照primefaces-test示例项目调整
pom.xml依赖配置后,启动阶段空指针异常消除,但浏览器访问应用时在RENDER_RESPONSE(6)阶段抛出javax.faces.FacesException
当前环境配置
- 核心组件版本:MyFaces 2.3.10、MyFaces CODI 1.0.5、PrimeFaces 4.0、OpenWebBeans 2.0.27、Tomcat 9、Java 8
- 配置说明:
beans.xml沿用原有1.0规范配置;所有依赖Jar均放置在Web应用WEB-INF/lib目录(仅该放置方式可正常启动),未放入Tomcat全局lib目录 - 待解决事项:原WebLogic自带的JAX-RS相关Jar需替换为Tomcat适配实现;
WEB-INF/lib下13个手动添加依赖缺乏规范管理方案
解决方案
1. 启动阶段空指针异常修复
针对BeanManagerImpl.wrapExpressionFactory触发的空指针
该异常核心原因是MyFaces 2.3与OpenWebBeans 2.0.x集成时EL表达式工厂初始化顺序冲突:OpenWebBeans尝试包装EL表达式工厂时,JSF上下文未完成初始化,导致获取到的ExpressionFactory实例为null。
修复操作:
- 在
web.xml中调整监听器加载顺序,将OpenWebBeans的WebBeansConfigurationListener配置在MyFaces的StartupServletContextListener之前,保证CDI容器先于JSF上下文完成初始化 - 移除
WEB-INF/lib中所有单独引入的EL相关冲突jar包(包括el-api.jar、el-impl.jar),Tomcat 9自带的EL 3.0实现完全兼容MyFaces 2.3,重复引入会触发类加载冲突
针对初始版本calculateFacesServletMapping触发的空指针
该问题是MyFaces 2.0.0的已知bug,当Servlet映射匹配逻辑在容器初始化阶段提前触发时,无法正确获取请求上下文的Servlet路径,升级到2.0.25及以上版本即可修复,无需额外调整配置。
2. RENDER_RESPONSE阶段FacesException修复
该异常通常由MyFaces CODI 1.0.5与MyFaces 2.3版本不兼容导致:MyFaces CODI 1.0.5是针对JSF 2.0/2.1版本开发的扩展,JSF 2.3对生命周期阶段的事件触发逻辑做了调整,CODI的ViewAccessScope、PhaseListener实现会在渲染阶段触发类转换或空值异常。
可二选一修复:
- 方案一(兼容性最优):将MyFaces版本回退到2.1.28(2.1分支最终稳定版),该版本与CODI 1.0.5、PrimeFaces 4.0、OpenWebBeans 2.0.27完全兼容,不需要修改原有业务代码
- 方案二(保留高版本):移除MyFaces CODI依赖,将CODI提供的CDI Scope(如ViewAccessScoped、WindowScoped)替换为MyFaces 2.3自带的CDI扩展实现,或升级到Apache DeltaSpike(CODI的后续继任项目)对应兼容版本
3. JAX-RS实现替换
Tomcat本身不自带JAX-RS实现,无需复杂适配,直接在Maven依赖中引入RESTEasy 3.15.x(兼容Java 8、Tomcat 9,符合JAX-RS 2.1规范)即可,不需要将Jar放到Tomcat全局lib目录,放在应用WEB-INF/lib下即可正常运行。注意排除依赖中自带的Servlet、CDI API类,避免与Tomcat、OpenWebBeans自带的API冲突。
4. 依赖管理最佳实践
不要手动维护WEB-INF/lib下的Jar包,全部通过Maven统一管理:
- 所有业务依赖的scope设置为
compile,构建时会自动拷贝到WEB-INF/lib目录,避免手动拷贝导致的版本遗漏、冲突问题 - 借助Maven的
dependency:tree命令排查依赖冲突,排除重复引入的不同版本Jar包 - 除JDBC驱动这类需要全局共享的组件外,不要将应用依赖放到Tomcat全局
lib目录,全局类加载优先级高于应用类加载器,会导致多应用部署时的版本冲突,其余业务依赖全部放在应用WEB-INF/lib下 - 对
beans.xml做小幅升级,将bean-discovery-mode设置为all,兼容原有1.0版本配置的同时,避免CDI 1.1+默认的注解扫描模式导致原有未加注解的Bean无法被识别的问题
注意:PrimeFaces 4.0发布时间早于MyFaces 2.3,若保留MyFaces 2.3版本,需要额外在
web.xml中配置org.apache.myfaces.EXPRESSION_FACTORY参数指向Tomcat自带的ExpressionFactory实现类,避免PrimeFaces自定义EL解析器触发类加载异常。
内容的提问来源于stack exchange,提问作者UserB

