JSF和类型安全

Zhe*_*dar 5 java generics jsf classcastexception type-safety

当我挣扎了好几个小时后,我终于找到了那些令人烦恼ClassCastException的东西来自哪里,我认为这些是由Hibernate制作的,而且它是enum映射的.

但是,他们都来自我的JSF视图,在这里我通过List

    <h:selectManyCheckbox value="#{createUserManager.user.roles}"  ... >
        <f:selectItems value="#{createUserManager.roles}"/>
    </h:selectManyCheckbox>
Run Code Online (Sandbox Code Playgroud)

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

public void setRoles(List<Role> paramRoles) {

    System.out.println(paramRoles.get(0) instanceof Role); //output: false

    for(Role role : paramRoles){ ...} //crashes with ClassCastException
}
Run Code Online (Sandbox Code Playgroud)

更改List<Role> paramRolesList<String> paramRoles完美.
这怎么可能?这些泛型不应该是类型安全的还是与JSF有关的类型擦除会导致整个类型的安全问题?
也不应的返回值h:selectManyCheckboxList<Role>,像我通过传递f:selectItems

sku*_*sel 7

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

问题

  1. HTTP部分

    问题是您不完全了解HTTP的工作原理.当您按下提交按钮提交数据,通过JSF生成您的参数<h:selectManyCheckbox>标签,如一堆<input type="checkbox" name="..." value="userRoleAsString">复选框,将发送作为字符串retrived作为最终request.getParameter("checkboxName");也作为字符串.当然,JSF不知道如何构建模型对象类Role.

  2. 泛型部分

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

  3. JSF部分:善后

    JSF有一个提供的javax.faces.Enum转换器,如果EL知道你的列表的编译时泛型类型,确实可行Role.但它不知道它.Role[] userRoles如果您在对象上进行多项选择,或者您使用了<h:selectOneMenu>与值绑定的唯一选择,则没有必要提供转换器Role userRole.在这些示例中,将自动调用内置枚举转换器.

    因此,为了得到它的工作预期,您需要提供一个Converter将"解释" JSF是什么类型的值做这个列表保持,以及如何从做转换RoleString,反之亦然.

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


Stack Overflow的参考点

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


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

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

风景:

<h:form>
    Many with enum converter
    <!-- will be mapped correctly with Role object -->
    <h:selectManyCheckbox value="#{q16433250Bean.userRoles}" converter="roleEnumConverter">
        <f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
    </h:selectManyCheckbox>
    <br/>
    Many with plain converter
    <!-- will be mapped correctly with Role object -->
    <h:selectManyCheckbox value="#{q16433250Bean.userRoles2}" converter="roleConverter">
        <f:selectItems value="#{q16433250Bean.allRoles2}" var="role" itemLabel="#{role.name}" />
    </h:selectManyCheckbox>
    <br/>
    Without any converter
    <!-- will NOT be mapped correctly with Role object, but with a default String instead -->
    <h:selectManyCheckbox value="#{q16433250Bean.userRoles3}">
        <f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
    </h:selectManyCheckbox>
    <br/>
    Without any converter + array
    <!-- will be mapped correctly with Role object -->
    <h:selectManyCheckbox value="#{q16433250Bean.userRoles4}">
        <f:selectItems value="#{q16433250Bean.allRoles}" var="role" itemLabel="#{role.name}" />
    </h:selectManyCheckbox>
    <br/>
    <h:commandButton value="Submit" action="#{q16433250Bean.action}"/>
</h:form>
Run Code Online (Sandbox Code Playgroud)

豆子:

@ManagedBean
@RequestScoped
public class Q16433250Bean {

    private List<Role> userRoles = new ArrayList<Role>();//getter + setter
    private List<Role> userRoles2 = new ArrayList<Role>();//getter + setter
    private List<Role> userRoles3 = new ArrayList<Role>();//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;
    }

}
Run Code Online (Sandbox Code Playgroud)

转换器:

@FacesConverter("roleEnumConverter")
public class RoleEnumConverter extends EnumConverter {

    public RoleEnumConverter() {
        super(Role.class);
    }

}
Run Code Online (Sandbox Code Playgroud)

@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();
    }

}
Run Code Online (Sandbox Code Playgroud)