【问题标题】:JSF and type safetyJSF 和类型安全
【发布时间】:2023-03-15 20:33:01
【问题描述】:

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

但它们来自我的 JSF 视图,我从

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

回到我的支持bean。
我的数据仅包含枚举的值: public Role[] getRoles() { return Role.values(); } .
当我在User-class 中测试roles 的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&lt;Role&gt; paramRoles 更改为 List&lt;String&gt; paramRoles 效果很好。
这怎么可能?这些泛型不应该是类型安全的,还是与 JSF 相关的类型擦除会扼杀整个类型安全?
h:selectManyCheckbox的返回值也不应该是List&lt;Role&gt;,就像我通过f:selectItems传入的一样?

【问题讨论】:

标签: java jsf generics classcastexception type-safety


【解决方案1】:

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

问题

  1. HTTP 部分

    问题在于您并不完全了解 HTTP 的工作原理。当您通过按下提交按钮提交数据时,您由 JSF &lt;h:selectManyCheckbox&gt; 标签生成的参数,作为一堆 &lt;input type="checkbox" name="..." value="userRoleAsString"&gt; 复选框,将被发送作为字符串最终作为@检索987654327@ 也是字符串。当然,JSF 不知道如何构造模型对象类,Role

  2. 泛型部分

    正如您所知,由于 java 为泛型选择了 类型擦除 以提供向后兼容性,因此有关泛型类型的信息基本上是编译类型的工件,并且在运行时会丢失。所以在运行时你的List&lt;Role&gt; 会被删除为一个普通的旧List。就 EL 是一种使用 Java 反射 API 来处理您的表达式/调用方法的运行时语言而言,在运行时没有这样的信息可用。考虑到 HTTP 部分,JSF 尽其所能并将字符串对象分配给您的列表,因为这是它可以隐式执行的所有操作。如果你愿意告诉 JSF 不这样做,你需要明确地这样做,即通过指定一个转换器来知道在 HTTP 请求中期望什么类型的对象

  3. JSF 部分:后果

    JSF 有一个提供的javax.faces.Enum 转换器,如果 EL 知道你的列表的编译时泛型类型,那就是Role,它确实可以工作。但它不知道。如果您在Role[] userRoles 对象上完成多项选择,或者如果您使用像&lt;h:selectOneMenu&gt; 中的唯一选择,并且值绑定到Role userRole,则无需提供转换器。在这些示例中,内置的枚举转换器将被自动调用。

    因此,要使其按预期工作,您需要提供一个Converter,它将“解释”JSF 该列表包含什么类型的值,以及如何进行从RoleString 的转换,反之亦然。

    值得注意的是,多选 JSF 组件中的任何绑定 List&lt;...&gt; 值都会出现这种情况。


Stack Overflow 上的参考点

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


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

下面我提供了一个测试用例,完全是为了让您理解:除了第三个&lt;h:selectManyCheckbox&gt; 组件之外,一切都按预期工作。完全跟踪它以完全消除问题取决于您。

观点:

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

}

【讨论】:

  • 标准枚举转换器不应该在没有明确设置的情况下在 JSF 2.0 中执行此操作吗?
  • 不,它不应该在这种特殊情况下。请参阅上面的扩展答案。
  • 嗯,我知道转换器的一般工作原理(以及如何像我已经做过的那样编写它们),但我错误地认为标准枚举转换器无需任何额外工作就可以为我做到这一点。但是这个答案是类似 BalusC 的质量,谢谢 :)
  • Java Enum 不需要转换器。
猜你喜欢
  • 2011-02-17
  • 2016-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-20
  • 2010-10-26
  • 1970-01-01
  • 2021-12-26
相关资源
最近更新 更多