Java JSF与类型安全

Java JSF与类型安全,java,jsf,generics,classcastexception,type-safety,Java,Jsf,Generics,Classcastexception,Type Safety,当我挣扎了几个小时后,我终于找到了那些恼人的ClassCastExceptions的来源,我认为它们是由Hibernate生成的,并且是enum-映射 但是它们来自我的JSF视图,在那里我从 <h:selectManyCheckbox value="#{createUserManager.user.roles}" ... > <f:selectItems value="#{createUserManager.roles}"/> </

当我挣扎了几个小时后,我终于找到了那些恼人的
ClassCastException
s的来源,我认为它们是由Hibernate生成的,并且是
enum
-映射

但是它们来自我的JSF视图,在那里我从

    <h:selectManyCheckbox value="#{createUserManager.user.roles}"  ... >
        <f:selectItems value="#{createUserManager.roles}"/>
    </h:selectManyCheckbox>
列表参数角色
更改为
列表参数角色
效果很好。
这怎么可能?那些泛型不应该是类型安全的吗?或者与JSF相关的类型擦除会扼杀整个类型安全吗?

h:selectManyCheckbox
的返回值不应该是
List
,就像我通过
f:selectItems
传入的那样吗?

您所经历的行为完全是意料之中的。此外,它与java泛型的关系与HTTP的工作方式相同

问题
  • HTTP部分

    问题是您没有完全理解HTTP是如何工作的。当您通过按submit按钮提交数据时,JSF
    标记生成的参数作为一组
    复选框,将作为字符串发送,并最终作为
    request.getParameter(“checkboxName”)检索也作为字符串。当然,JSF不知道如何构造模型对象类,
    Role

  • 泛型部分

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

  • JSF部分:后果

    JSF提供了一个
    javax.faces.Enum
    转换器,如果EL知道列表的编译时泛型类型,那么它确实可以工作,即
    Role
    。但它不知道。如果在
    角色[]用户角色
    对象上进行多个选择,或者使用像
    中那样的唯一选择并将值绑定到
    角色
    ,则无需提供转换器。在这些示例中,将自动调用内置的枚举转换器

    因此,为了让它按预期工作,您需要提供一个“解释”JSF这个列表包含什么类型的值,以及如何从
    角色
    转换到
    字符串
    ,反之亦然

    值得注意的是,对于多选JSF组件中的任何绑定的
    List
    值,情况都是如此


  • 堆栈溢出的参考点 在问题被检查和解决后,我想知道过去是否没有人面对它,并在这里寻找以前的答案。毫不奇怪,以前有人问过这个问题,当然这个问题是由巴卢斯克解决的。以下是两个最有价值的参考点:

    • )
    • )

    测试用例和两个工作转换器示例 下面我提供了一个完全供您理解的测试用例:除了第三个
    组件之外,其他一切都按预期工作。这取决于您如何全面跟踪,以彻底消除该问题

    观点:

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


    我不了解JSF,所以我不能很好地推测,但是JSF对泛型的支持看起来非常糟糕:标准enum转换器在JSF2.0中不应该在没有明确设置的情况下这样做吗?不,在这种特殊情况下不应该这样做。请看上面的扩展答案。我知道转换器一般是如何工作的(以及如何像我已经做的那样编写它们),但我错了,因为我认为标准enum转换器可以在不做任何额外工作的情况下为我完成这项工作。不过,这个答案与BalusC类似,谢谢:)Java枚举不需要转换器。
    <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);
        }
    
    }
    
    @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();
        }
    
    }