JSF和类型安全 [英] JSF and type safety

查看:86
本文介绍了JSF和类型安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我挣扎了几个小时后,终于找到了那些令人讨厌的 ClassCastException 来自哪里,我认为这些是由Hibernate生成的,它是 enum code> -mapping。



但是他们来自我的JSF视图,我从那里传入了 List

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

返回到我的支持bean。

我的数据只包含一个枚举:
public Role [] getRoles()
{
return Role.values();
}


当我在<$ c $中测试角色的setter时,我感到非常震惊c> User
-class并得到这个:

  public void setRoles(List< Role> paramRoles) {

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

(角色角色:paramRoles){...} //使用ClassCastException崩溃
}

更改列表< Role> paramRoles List< String> paramRoles 完美工作。

这可能如何?不应该那些泛型是类型安全的还是与JSF有关的类型擦除,以杀死整个类型安全的东西?

也不应该返回值 h:selectManyCheckbox List< Role> ,就像我通过 f:selectItems

解决方案

您所遇到的行为完全可以预料。此外,它与HTTP泛型相关,与HTTP的工作方式相关。



问题




  1. HTTP部分

    问题是您没有完全理解HTTP的工作原理。当您通过按提交按钮提交数据时,由JSF < h:selectManyCheckbox> 标记生成的参数,作为一群<输入type =checkboxname =...value =userRoleAsString> 复选框将发送字符串最终以 request.getParameter(checkboxName); 也作为字符串。当然,JSF不知道如何构建模型对象类, Role


  2. 泛型部分



    正如您所知道的,由于java为泛型提供了向后兼容性选择 type erasure 关于泛型类型的信息基本上是一个编译类型的工件,并在运行时丢失。所以在运行时,你的 List< Role> 会清除为一个普通的旧的 List 。就EL而言,它是一种使用Java Reflection API来处理表达式/调用方法的运行时语言,因此在运行时不提供此类信息。考虑到HTTP部分,JSF会尽最大努力并将字符串对象分配给您的列表,因为它可以隐式执行。如果你愿意告诉JSF做其他事情,你需要明确地做到这一点,即通过指定一个转换器来知道在HTTP请求中需要什么类型的对象

  3. >
  4. JSF部分:aftermath



    JSF提供了 javax。 faces.Enum 转换器,实际上,如果EL知道列表的编译时泛型类型,那就是 Role 。但它不知道它。如果您的多重选择将在 Role [] userRoles 对象上完成,或者如果您使用了诸如<$的唯一选择,则无需提供转换器c $ c>< h:selectOneMenu> ,其值绑定到角色userRole 。在这些例子中,内置的枚举转换器将被自动调用。

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



    值得注意的是,任何绑定 List< ...>






点数对Stack Overflow的引用



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






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



下面我提供了一个完整的理解测试用例:除了第三个< h:selectManyCheckbox> ; 组件。这是完全追踪它完全消除这个问题。



该视图:

 < H:形式> 
许多使用枚举转换器
<! - 将被正确映射到角色对象 - >
< h:selectManyCheckbox value =#{q16433250Bean.userRoles}converter =roleEnumConverter>
< / h:selectManyCheckbox>
< br />
许多使用普通转换器
<! - 将被正确映射到角色对象 - >
< h:selectManyCheckbox value =#{q16433250Bean.userRoles2}converter =roleConverter>
< / h:selectManyCheckbox>
< br />
没有任何转换器
<! - 将不会被正确映射到Role对象,而是使用默认的String来代替 - >
< h:selectManyCheckbox value =#{q16433250Bean.userRoles3}>
< / h:selectManyCheckbox>
< br />
没有任何转换器+数组
<! - 将被正确映射到角色对象 - >
< h:selectManyCheckbox value =#{q16433250Bean.userRoles4}>
< / h:selectManyCheckbox>
< br />
< h:commandButton value =Submitaction =#{q16433250Bean.action}/>
< / h:表格>

该bean:

  @ManagedBean 
@RequestScoped
公共类Q16433250Bean {

私人列表< 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角色{

ADMIN(Admin),
SUPER_USER(超级用户),
USER(用户);
private final String name;

私人角色(字符串名称){
this.name = name;
}

public String getName(){
return this.name;


$ b $ public Role [] getAllRoles(){
return Role.values();
}

public String action(){
return null;
}

}

转换器:



pre $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $
super(Role.class);
}

}

  @FacesConverter(roleConverter)
public class RoleConverter实现Converter {

public Object getAsObject(FacesContext context, UIComponent组件,字符串值){
if(value == null || value.equals()){
return null;
}
角色角色= Role.valueOf(value);
返回角色;

$ b $ public String getAsString(FacesContext context,UIComponent component,Object value){
if(!(value instanceof Role)||(value == null)){
返回null;
}
return((Role)value).toString();
}

}


As I struggled for hours I finally found where those annoying ClassCastExceptions came from, which I thought were produced by Hibernate and it's enum-mapping.

But they came from my JSF view, where I passed a List from

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

back into my backing bean.
My data simply consists of the values of an enum: public Role[] getRoles() { return Role.values(); } .
I was really shocked when I tested the setter of roles in the User-class and got this:

public void setRoles(List<Role> paramRoles) {

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

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

Changing List<Role> paramRoles to List<String> paramRoles worked perfectly.
How is this possible? Shouldn't those generics be type safe or is type erasure in connection with JSF killing the whole type safety thing?
Also shouldn't the return value of h:selectManyCheckbox be List<Role>, like I passed in via the f:selectItems?

解决方案

The behaviour you are experiencing is fully expected. Moreover, it is related to java generics in the same way as to how HTTP works.

The problem

  1. The HTTP part

    The problem is that you don't fully understand how HTTP works. When you submit data by pressing the submit button, parameters of your generated by JSF <h:selectManyCheckbox> tag, as a bunch of <input type="checkbox" name="..." value="userRoleAsString"> checkboxes, will be sent as strings and retrived ultimately as request.getParameter("checkboxName"); also as strings. Of course, JSF has no idea how to construct you model object class, Role.

  2. The generics part

    As you know due to the fact that java chose type erasure for generics to provide for backwards compatibility, information about generic types is basically a compile-type artifact and is lost at runtime. So at runtime your List<Role> erases to a plain, good old List. And as far as EL is a runtime language that uses Java Reflection API to deal with your expressions / call methods, at runtime no such information is available. Taking into account the HTTP part, JSF does its best and assigns string objects to your list, as it's all it can implicitly do. If you are willing to tell JSF to do otherwise, you need to do that explicitly, i.e. by specifying a converter to know what type of object to expect in an HTTP request.

  3. The JSF part: aftermath

    JSF has a provided javax.faces.Enum converter and in indeed works, if EL knew of the compile-time generic type of your list, that is Role. But it doesn't know of it. It would be not necessary to provide for a converter in case your multiple selection would be done on a Role[] userRoles object, or if you used the unique selection like in <h:selectOneMenu> with a value bound to Role userRole. In these examples the built-in enum converter will be called automatically.

    So, to get it work as expected you need to provide for a Converter that will 'explain' JSF what type of values does this list hold, and how to do the transformations from Role to String, and vice versa.

    It is worth noting that this will be the case with any bound List<...> values within the multiple choice JSF components.


Points of reference on Stack Overflow

After the problem was examined and resolved I was wondering if no one faced it in the past and searched for some previous answers here. Not surprisingly, it was asked before, and of course the problem was solved by BalusC. Below are two most valuable point of reference:


The test case and two examples of working converters

Below I provide for a test case entirely for your understanding: everything works as expected apart from the third <h:selectManyCheckbox> component. It's up to you to trace it fully to eliminate the issue altogether.

The view:

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

The bean:

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

}

The converters:

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

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

}

and

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

}

这篇关于JSF和类型安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆