JSF和类型安全

时间:2013-05-08 05:25:07

标签: java jsf generics classcastexception type-safety

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

但是他们来自我的JSF视图,我从

传递List
    <h:selectManyCheckbox value="#{createUserManager.user.roles}"  ... >
        <f:selectItems value="#{createUserManager.roles}"/>
    </h:selectManyCheckbox>

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

public void setRoles(List<Role> paramRoles) {

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

    for(Role role : paramRoles){ ...} //crashes with ClassCastException
}

List<Role> paramRoles更改为List<String> paramRoles非常有效 这怎么可能?这些泛型不应该是类型安全的还是与JSF有关的类型擦除会导致整个类型的安全事件? 也不应该h:selectManyCheckbox的返回值为List<Role>,就像我通过f:selectItems传入的一样?

1 个答案:

答案 0 :(得分:7)

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

问题

  1. HTTP部分

    问题是您不能完全理解HTTP的工作原理。当您按提交按钮提交数据时,JSF <h:selectManyCheckbox>标记生成的参数(作为一堆<input type="checkbox" name="..." value="userRoleAsString">复选框)将以作为字符串发送最终以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>中的唯一选择且值绑定到{{1},则无需提供转换器。 }。在这些示例中,将自动调用内置枚举转换器。

    因此,为了让它按预期工作,您需要提供一个Converter来解释&#39; JSF此列表包含哪些类型的值,以及如何从Role userRole转换为Role,反之亦然。

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


  4. Stack Overflow

    的参考点

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


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

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

    观点:

    <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>
    

    转换器:

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

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