p:selectManyMenu 和@FacesConverter(forClass = Clazz.class)
p:selectManyMenu and @FacesConverter(forClass = Clazz.class)
<p:selectManyMenu id="colourList"
var="color"
value="#{testBean.selectedColours}"
converter="#{colourConverter}"
showCheckbox="true"
required="true"
label="Colour"
style="overflow: auto; width: 317px; background-color: white; max-height: 200px;">
<f:selectItems var="colour"
value="#{testBean.colours}"
itemLabel="#{colour.colourHex}"
itemValue="#{colour}"/>
<p:column>
<span style="display: inline-block; width: 275px; height: 20px; background-color:\##{color.colourHex}; border: 1px solid black;"
title="Name: #{color.colourName} | Hex: #{color.colourHex}" />
</p:column>
</p:selectManyMenu>
<p:commandButton value="Submit" actionListener="#{testBean.action}"/>
CSS原封不动,如果有人想把这个例子付诸实践的话。它将显示三种基本颜色 (RGB),前面有复选框,如下所示。
托管 bean:
@Named
@ViewScoped
public class TestBean implements Serializable {
@Inject
private DataStore dataStore;
private List<Colour> colours; //Getter & setter.
private List<Colour> selectedColours; //Getter & setter.
private static final long serialVersionUID = 1L;
public TestBean() {}
@PostConstruct
private void init() {
colours = dataStore.getColours();
}
public void action() {
for (Colour colour : selectedColours) {
System.out.println("colourName : "
+ colour.getColourName()
+ " : colourHex : "
+ colour.getColourHex());
}
}
}
如果从 <p:selectManyMenu>
中删除 converter="#{colourConverter}"
属性,那么它会导致 java.lang.ClassCastException
在 action()
方法中抛出,即使转换器用 @FacesConverter(forClass = Colour.class)
.
java.lang.ClassCastException: java.lang.String cannot be cast to com.example.Colour
看来是泛型橡皮擦问题(List<Colour>
的泛型类型参数在运行时被去掉,变成无类型的List
)。
Colour[]
应该可以工作,但是在尝试时 action()
方法本身没有被调用。
它需要明确提及转换器的确切原因是什么?
补充:
转换器:
@Named
@ApplicationScoped
@FacesConverter(forClass = Colour.class)
public class ColourConverter implements Converter {
@Inject
private DataStore dataStore;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
long parsedValue = Long.parseLong(value);
if (parsedValue <= 0) {
throw new ConverterException("FacesMessage");
}
Colour entity = dataStore.findColourById(parsedValue);
if (entity == null) {
throw new ConverterException("FacesMessage");
}
return entity;
} catch (NumberFormatException e) {
throw new ConverterException("FacesMessage", e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (!(value instanceof Colour)) {
throw new ConverterException("Message");
}
Long id = ((Colour) value).getColourId();
return id != null ? id.toString() : "";
}
}
维护 List<Colour>
的应用程序范围 bean。
@Named
@ApplicationScoped
public class DataStore {
private List<Colour> colours;
public DataStore() {}
@PostConstruct
private void init() {
colours = new ArrayList<>();
Colour colour = new Colour();
colour.setColourId(1L);
colour.setColourName("Red");
colour.setColourHex("FF0000");
colours.add(colour);
colour = new Colour();
colour.setColourId(3L);
colour.setColourName("Green");
colour.setColourHex("008000");
colours.add(colour);
colour = new Colour();
colour.setColourId(2L);
colour.setColourName("Blue");
colour.setColourHex("0000FF");
colours.add(colour);
}
public Colour findColourById(Long id) {
for (Colour colour : colours) {
if (colour.getColourId().equals(id)) {
return colour;
}
}
return null;
}
public List<Colour> getColours() {
return colours;
}
}
领域模型class:
public class Colour implements Serializable {
private Long colourId;
private String colourName;
private String colourHex;
private static final long serialVersionUID = 1L;
public Colour() {}
public Long getColourId() {
return colourId;
}
public void setColourId(Long colourId) {
this.colourId = colourId;
}
public String getColourName() {
return colourName;
}
public void setColourName(String colourName) {
this.colourName = colourName;
}
public String getColourHex() {
return colourHex;
}
public void setColourHex(String colourHex) {
this.colourHex = colourHex;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + Objects.hashCode(getColourId());
return hash;
}
@Override
public boolean equals(Object that) {
if (!(that instanceof Colour)) {
return false;
}
return this == that || Objects.equals(getColourId(), ((Colour) that).getColourId());
}
@Override
public String toString() {
return String.format("%s[colourId=%d]", getClass().getCanonicalName(), getColourId());
}
}
这个问题有两个方面。
第一个问题是EL无法确定模型值类型,因为泛型类型信息在运行时会丢失。基本上变成了Object.class
。您基本上需要将 List<Colour>
替换为 Colour[]
。这个问题详细回答了这个问题:UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T.
第二个问题是 PrimeFaces InputRenderer
有一个错误,它在定位转换器之前没有考虑数组类型。下面的行号匹配 5.2
156 protected Converter findImplicitConverter(FacesContext context, UIComponent component) {
157 ValueExpression ve = component.getValueExpression("value");
158
159 if(ve != null) {
160 Class<?> valueType = ve.getType(context.getELContext());
161
162 if(valueType != null)
163 return context.getApplication().createConverter(valueType);
164 }
165
166 return null;
167 }
在您的特定情况下,valueType
是 Colour[].class
而不是 Colour.class
。这解释了为什么它无法找到与 Colour.class
关联的转换器。在创建转换器之前,它应该检查 valueType
是否是来自它的 array type and if so then extract the component type。
if (valueType.isArray()) {
valueType = valueType.getComponentType();
}
你最好将此作为错误报告给 PrimeFaces 人员,同时说明它在 <h:selectManyMenu>
等标准组件中运行良好。同时,最好的办法是明确注册转换器。
<p:selectManyMenu id="colourList"
var="color"
value="#{testBean.selectedColours}"
converter="#{colourConverter}"
showCheckbox="true"
required="true"
label="Colour"
style="overflow: auto; width: 317px; background-color: white; max-height: 200px;">
<f:selectItems var="colour"
value="#{testBean.colours}"
itemLabel="#{colour.colourHex}"
itemValue="#{colour}"/>
<p:column>
<span style="display: inline-block; width: 275px; height: 20px; background-color:\##{color.colourHex}; border: 1px solid black;"
title="Name: #{color.colourName} | Hex: #{color.colourHex}" />
</p:column>
</p:selectManyMenu>
<p:commandButton value="Submit" actionListener="#{testBean.action}"/>
CSS原封不动,如果有人想把这个例子付诸实践的话。它将显示三种基本颜色 (RGB),前面有复选框,如下所示。
托管 bean:
@Named
@ViewScoped
public class TestBean implements Serializable {
@Inject
private DataStore dataStore;
private List<Colour> colours; //Getter & setter.
private List<Colour> selectedColours; //Getter & setter.
private static final long serialVersionUID = 1L;
public TestBean() {}
@PostConstruct
private void init() {
colours = dataStore.getColours();
}
public void action() {
for (Colour colour : selectedColours) {
System.out.println("colourName : "
+ colour.getColourName()
+ " : colourHex : "
+ colour.getColourHex());
}
}
}
如果从 <p:selectManyMenu>
中删除 converter="#{colourConverter}"
属性,那么它会导致 java.lang.ClassCastException
在 action()
方法中抛出,即使转换器用 @FacesConverter(forClass = Colour.class)
.
java.lang.ClassCastException: java.lang.String cannot be cast to com.example.Colour
看来是泛型橡皮擦问题(List<Colour>
的泛型类型参数在运行时被去掉,变成无类型的List
)。
Colour[]
应该可以工作,但是在尝试时 action()
方法本身没有被调用。
它需要明确提及转换器的确切原因是什么?
补充:
转换器:
@Named
@ApplicationScoped
@FacesConverter(forClass = Colour.class)
public class ColourConverter implements Converter {
@Inject
private DataStore dataStore;
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
long parsedValue = Long.parseLong(value);
if (parsedValue <= 0) {
throw new ConverterException("FacesMessage");
}
Colour entity = dataStore.findColourById(parsedValue);
if (entity == null) {
throw new ConverterException("FacesMessage");
}
return entity;
} catch (NumberFormatException e) {
throw new ConverterException("FacesMessage", e);
}
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (!(value instanceof Colour)) {
throw new ConverterException("Message");
}
Long id = ((Colour) value).getColourId();
return id != null ? id.toString() : "";
}
}
维护 List<Colour>
的应用程序范围 bean。
@Named
@ApplicationScoped
public class DataStore {
private List<Colour> colours;
public DataStore() {}
@PostConstruct
private void init() {
colours = new ArrayList<>();
Colour colour = new Colour();
colour.setColourId(1L);
colour.setColourName("Red");
colour.setColourHex("FF0000");
colours.add(colour);
colour = new Colour();
colour.setColourId(3L);
colour.setColourName("Green");
colour.setColourHex("008000");
colours.add(colour);
colour = new Colour();
colour.setColourId(2L);
colour.setColourName("Blue");
colour.setColourHex("0000FF");
colours.add(colour);
}
public Colour findColourById(Long id) {
for (Colour colour : colours) {
if (colour.getColourId().equals(id)) {
return colour;
}
}
return null;
}
public List<Colour> getColours() {
return colours;
}
}
领域模型class:
public class Colour implements Serializable {
private Long colourId;
private String colourName;
private String colourHex;
private static final long serialVersionUID = 1L;
public Colour() {}
public Long getColourId() {
return colourId;
}
public void setColourId(Long colourId) {
this.colourId = colourId;
}
public String getColourName() {
return colourName;
}
public void setColourName(String colourName) {
this.colourName = colourName;
}
public String getColourHex() {
return colourHex;
}
public void setColourHex(String colourHex) {
this.colourHex = colourHex;
}
@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + Objects.hashCode(getColourId());
return hash;
}
@Override
public boolean equals(Object that) {
if (!(that instanceof Colour)) {
return false;
}
return this == that || Objects.equals(getColourId(), ((Colour) that).getColourId());
}
@Override
public String toString() {
return String.format("%s[colourId=%d]", getClass().getCanonicalName(), getColourId());
}
}
这个问题有两个方面。
第一个问题是EL无法确定模型值类型,因为泛型类型信息在运行时会丢失。基本上变成了Object.class
。您基本上需要将 List<Colour>
替换为 Colour[]
。这个问题详细回答了这个问题:UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T.
第二个问题是 PrimeFaces InputRenderer
有一个错误,它在定位转换器之前没有考虑数组类型。下面的行号匹配 5.2
156 protected Converter findImplicitConverter(FacesContext context, UIComponent component) {
157 ValueExpression ve = component.getValueExpression("value");
158
159 if(ve != null) {
160 Class<?> valueType = ve.getType(context.getELContext());
161
162 if(valueType != null)
163 return context.getApplication().createConverter(valueType);
164 }
165
166 return null;
167 }
在您的特定情况下,valueType
是 Colour[].class
而不是 Colour.class
。这解释了为什么它无法找到与 Colour.class
关联的转换器。在创建转换器之前,它应该检查 valueType
是否是来自它的 array type and if so then extract the component type。
if (valueType.isArray()) {
valueType = valueType.getComponentType();
}
你最好将此作为错误报告给 PrimeFaces 人员,同时说明它在 <h:selectManyMenu>
等标准组件中运行良好。同时,最好的办法是明确注册转换器。