JSF和类型安全

当我挣扎了好几个小时后,我终于找到了那些烦人的ClassCastException来自哪里,我认为这是由Hibernate生成的,它是enum映射。

但它们来自我的JSF视图,我在其中传递了一个List

     

回到我的支持bean。
我的数据只包含枚举的值: public Role[] getRoles() { return Role.values(); } public Role[] getRoles() { return Role.values(); }
当我在User -class中测试roles的setter并得到这个时,我感到非常震惊:

 public void setRoles(List paramRoles) { System.out.println(paramRoles.get(0) instanceof Role); //output: false for(Role role : paramRoles){ ...} //crashes with ClassCastException } 

List paramRoles更改为List paramRoles工作得很好。
这怎么可能? 这些generics不应该是类型安全的还是与JSF相关的类型擦除会导致整个类型的安全问题?
也不应该将h:selectManyCheckbox的返回值设为List ,就像我通过f:selectItems传入的一样?

您正在经历的行为是完全可以预期的。 此外,它与javagenerics有关,与HTTP的工作方式相同。

问题

  1. HTTP部分

    问题是您不完全了解HTTP的工作原理。 当您通过按下提交按钮提交数据时,由JSF 标记生成的参数,作为一堆复选框,将作为字符串发送并最终作为request.getParameter("checkboxName"); 也作为字符串 。 当然,JSF不知道如何构建模型对象类Role

  2. generics部分

    如您所知,由于java为generics选择了类型擦除以提供向后兼容性,因此有关generics类型的信息基本上是编译类型工件,并且在运行时丢失。 因此,在运行时, List会擦除一个简单,好的旧List 。 至于EL是一种运行时语言,它使用Java Reflection API来处理表达式/调用方法,在运行时没有这样的信息可用。 考虑到HTTP部分,JSF尽力并将字符串对象分配给列表,因为它可以隐式地完成。 如果您愿意告诉JSF不这样做,您需要明确地这样做,即通过指定转换器来了解HTTP请求中期望的对象类型

  3. JSF部分:善后

    JSF有一个提供的javax.faces.Enum转换器,如果EL知道列表的编译时generics类型,那就是有效的。 但它不知道它。 如果您在Role[] userRoles对象上进行多项选择,或者如果您使用的唯一选择并且值绑定到Role userRole ,则没有必要提供转换器。 在这些示例中,将自动调用内置枚举转换器。

    因此,为了使其按预期工作,您需要提供一个Converter ,它将“解释”JSF此列表包含哪些类型的值,以及如何从Role转换为String ,反之亦然。

    值得注意的是,多选JSF组件中的任何绑定List<...>值都是这种情况。


Stack Overflow的参考点

在检查并解决了问题后,我想知道过去是否有人遇到过这个问题并在此处搜索了一些以前的答案。 毫不奇怪,它之前被问过,当然这个问题已经被BalusC解决了。 以下是两个最有价值的参考点:

  • JSF 2.0在selectMany菜单中使用enum ;
  • 如何在JSF中创建枚举的下拉菜单 ;
  • 如何在f:selectItems中为枚举创建和使用通用bean? 。

测试用例和工作转换器的两个例子

下面我提供了一个完全符合您理解的测试用例:除了第三个组件之外,一切都按预期工作。 由您完全跟踪它以完全消除该问题。

风景:

  Many with enum converter     
Many with plain converter
Without any converter
Without any converter + array

豆子:

 @ManagedBean @RequestScoped public class Q16433250Bean { private List userRoles = new ArrayList();//getter + setter private List userRoles2 = new ArrayList();//getter + setter private List userRoles3 = new ArrayList();//getter + setter private Role[] userRoles4;//getter + setter public enum Role { ADMIN("Admin"), SUPER_USER("Super user"), USER("User"); private final String name; private Role(String name) { this.name = name; } public String getName() { return this.name; } } public Role[] getAllRoles() { return Role.values(); } public String action() { return null; } } 

转换器:

 @FacesConverter("roleEnumConverter") public class RoleEnumConverter extends EnumConverter { public RoleEnumConverter() { super(Role.class); } } 

 @FacesConverter("roleConverter") public class RoleConverter implements Converter { public Object getAsObject(FacesContext context, UIComponent component, String value) { if(value == null || value.equals("")) { return null; } Role role = Role.valueOf(value); return role; } public String getAsString(FacesContext context, UIComponent component, Object value) { if (!(value instanceof Role) || (value == null)) { return null; } return ((Role)value).toString(); } }