PrimeFaces PickList在JBoss EAP 7.2渲染时出现NullPointerException问题
我碰到了和PrimeFaces PickList搭配OmniFaces validateAll时触发NPE类似的问题,调试PickListRenderer时出现完全一致的症状。虽然Thomas Andraschko曾认为这是Mojarra的问题,但我切换到MyFaces 2.2和2.3版本后,错误依然存在。
已尝试的修复方案
- 替换Mojarra为MyFaces
- 将PrimeFaces从7.0升级到8.0.RC1
- 添加自定义
PickListConverter - 通过
jboss-deployment-structure.xml禁用JBoss自带的相关依赖包
错误触发细节
错误在页面加载时直接触发,添加Converter是我尝试的修复手段,但实际上错误在Converter生效前就已出现——无论是否配置Converter,都会抛出NPE。
我的环境是:JBoss EAP 7.2,代码仓库地址为https://github.com/Qleoz12/Primefaces-Mydemo
错误详情
始终抛出java.lang.NullPointerException,根源在org.primefaces.component.picklist.PickListRenderer.encodeMarkup(PickListRenderer.java:103)。调试发现encodeMarkup方法中的DualListModel model始终为null,调用encodeList时触发空指针,核心堆栈信息如下:
java.lang.NullPointerException
at org.primefaces.component.picklist.PickListRenderer.encodeMarkup(PickListRenderer.java:103)
at org.primefaces.component.picklist.PickListRenderer.encodeEnd(PickListRenderer.java:74)
at javax.faces.component.UIComponentBase.encodeEnd(UIComponentBase.java:919)
...
相关代码
XHTML页面代码
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:ccp="http://java.sun.com/jsf/composite/cc" template="../template/ui.xhtml"> <ui:define name="body"> <p:pickList id="FF" value="#{CompositeComponent.cities}" var="cities" itemLabel="#{cities}" itemValue="#{cities}" converter="PickListConverter" > </p:pickList> </ui:define> </ui:composition>
Bean代码
package Beans; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.inject.Named; import org.primefaces.model.DualListModel; @Named @javax.faces.view.ViewScoped public class CompositeComponent implements Serializable { private static final long serialVersionUID = 1L; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CompositeComponent.class); private DualListModel<String> cities; List<String> citiesSource = new ArrayList<String>(); List<String> citiesTarget = new ArrayList<String>(); public CompositeComponent() { super(); } @PostConstruct public void init() { citiesSource.add("San Francisco"); citiesSource.add("London"); citiesSource.add("Paris"); citiesSource.add("Istanbul"); citiesSource.add("Berlin"); citiesSource.add("Barcelona"); citiesSource.add("Rome"); cities = new DualListModel<String>(citiesSource, citiesTarget); } public DualListModel<String> getCities() { return cities; } public void setCities(DualListModel<String> cities) { this.cities = cities; } }
自定义Converter代码
package converter; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.FacesConverter; import org.primefaces.component.picklist.PickList; import org.primefaces.model.DualListModel; @SuppressWarnings({"unused", "rawtypes"}) @FacesConverter("PickListConverter") public class PickListConverter implements Converter{ public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue) { PickList p = (PickList) component; DualListModel dl = (DualListModel) p.getValue(); return dl.getSource().get(Integer.valueOf(submittedValue)); } public String getAsString(FacesContext facesContext, UIComponent component, Object value) { PickList p = (PickList) component; DualListModel dl = (DualListModel) p.getValue(); return String.valueOf(dl.getSource().indexOf(value)); } }
问题分析与可能的解决方案
从调试结果来看,PickListRenderer渲染时无法获取DualListModel实例,说明JSF没有正确将Bean中的cities属性绑定到组件上,核心可能原因如下:
1. EL表达式的Bean名称错误
JSF的@Named注解默认会将类名首字母小写作为Bean名称,也就是#{compositeComponent.cities},而不是XHTML中使用的大写开头#{CompositeComponent.cities}。这很可能是导致组件无法找到Bean属性的关键原因。
- 解决方案:将XHTML中的
value="#{CompositeComponent.cities}"改为value="#{compositeComponent.cities}",或者在@Named注解中显式指定名称:@Named("CompositeComponent")。
2. 视图作用域初始化异常
虽然使用了@PostConstruct初始化cities,但可能组件渲染时Bean还未完成初始化,或者视图作用域的生命周期出现异常。
- 解决方案:临时切换到
@SessionScoped验证是否是视图作用域的问题,同时确保项目依赖完整支持JSF 2.2+的视图作用域。
3. Converter空值处理缺失
即使当前错误不在Converter,也应该添加空值判断避免后续潜在的NPE:
public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue) { if (submittedValue == null || submittedValue.isEmpty()) { return null; } PickList p = (PickList) component; DualListModel dl = (DualListModel) p.getValue(); if (dl == null || dl.getSource() == null) { return null; } int index = Integer.parseInt(submittedValue); return index >= 0 && index < dl.getSource().size() ? dl.getSource().get(index) : null; } public String getAsString(FacesContext facesContext, UIComponent component, Object value) { if (value == null) { return ""; } PickList p = (PickList) component; DualListModel dl = (DualListModel) p.getValue(); return dl != null && dl.getSource() != null ? String.valueOf(dl.getSource().indexOf(value)) : ""; }
内容的提问来源于stack exchange,提问作者qleoz12

