【问题标题】:JSF 2.3 Custom converter with generics带有泛型的 JSF 2.3 自定义转换器
【发布时间】:2017-04-05 18:06:25
【问题描述】:

我们现在开始在我们现有的 JSF 2.2 项目中使用 JSF 2.3。在我们的自定义转换器上,我们收到了警告Converter is a raw type. References to generic type Converter<T> should be parameterized. 我们遇到的问题是当我们尝试使用泛型修复该警告时:

@FacesConverter(value = "myConverter", managed = true)
public class MyConverter implements Converter<MyCustomObject>{  

@Override
public MyCustomObject getAsObject(FacesContext context, UIComponent component, String submittedValue){}

@Override
public String getAsString(FacesContext context, UIComponent component, MyCustomObject modelValue) {}
}

当转换器用于例如

<!DOCTYPE html>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:selectOneMenu id="#{componentId}" value="#{componentValue}">
    <f:converter converterId="myConverter" />
    <f:selectItem itemLabel="label"
        itemValue="" />
    <f:selectItems value="listOfValues"
        var="singleValue"
        itemValue="singleValue.value"
        itemLabel="singleValue.label" />
</h:selectOneMenu>

然后ClassCastException 和消息java.lang.String cannot be cast to MyCustomObject 被抛出。 stacktrace 中还有一行可以帮助com.sun.faces.cdi.CdiConverter.getAsString(CdiConverter.java:109)

但是当转换器泛型定义从 MyCustomObject 更改为 Object 时:

@FacesConverter(value = "myConverter", managed = true)    
public class MyConverter implements Converter<Object>{  

@Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue){}

@Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {}
}

然后一切都按预期工作,但这显然超出了Converter&lt;T&gt; 接口的目的。

【问题讨论】:

  • 有什么想法吗?也许@arjan-tijms?
  • 顺便说一句:&lt;Object&gt; 非常通用,您可能希望在此处使用 POJO 或 POJI 而不是 Object。就像我对User(我的 POJI)所做的那样。

标签: jsf jakarta-ee cdi apache-tomee jsf-2.3


【解决方案1】:

我在这里遇到了同样的问题,并提出了以下解决方案,实际上不仅可以编译而且运行良好:

some_page.xhtml(相关摘录):

<h:selectOneMenu styleClass="select" id="companyUserOwner" value="#{adminCompanyDataController.companyUserOwner}">
    <f:converter converterId="UserConverter" />
    <f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
    <f:selectItems value="#{userController.allUsers()}" var="companyUserOwner" itemValue="#{companyUserOwner}" itemLabel="#{companyUserOwner.userContact.contactFirstName} #{companyUserOwner.userContact.contactFamilyName} (#{companyUserOwner.userName})" />
</h:selectOneMenu>

请注意,上面的 JSF 代码充满了自定义内容,可能无法在您的终端上运行。而且我的转换器编译时没有原始类型警告(JSF 2.3+,not 2.2!):

SomeUserConverter.java:

@FacesConverter (value = "UserConverter")
public class SomeUserConverter implements Converter<User> {

    /**
     * User EJB
     */
    private static UserSessionBeanRemote USER_BEAN;

    /**
     * Default constructor
     */
    public SomeUserConverter () {
    }

    @Override
    public User getAsObject (final FacesContext context, final UIComponent component, final String submittedValue) {
        // Is the value null or empty?
        if ((null == submittedValue) || (submittedValue.trim().isEmpty())) {
            // Return null
            return null;
        }

        // Init instance
        User user = null;

        try {
            // Try to parse the value as long
            final Long userId = Long.valueOf(submittedValue);

            // Try to get user instance from it
            user = USER_BEAN.findUserById(userId);
        } catch (final NumberFormatException ex) {
            // Throw again
            throw new ConverterException(ex);
        } catch (final UserNotFoundException ex) {
            // User was not found, return null
        }

        // Return it
        return user;
    }

    @Override
    public String getAsString (final FacesContext context, final UIComponent component, final User value) {
        // Is the object null?
        if ((null == value) || (String.valueOf(value).isEmpty())) {
            // Is null
            return ""; //NOI18N
        }

        // Return id number
        return String.valueOf(value.getUserId());
    }

}

这个转换器类删除了 JNDI 查找(无论如何我稍后会重写该部分)但它应该足以演示重要部分:

  • Converter&lt;User&gt;User 是自定义 POJI)防止原始类型警告
  • value="UserConverter" 这是您在 JSF 页面中使用的实际名称
  • 最重要的是,它实际上修复了ClassCastException
  • &lt;f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" /&gt;
  • 通过省略 #{null} 一个 空字符串 而不是 null 正在处理到您的 getAsString 方法!

最后一个实际上修复了上述异常,我的应用程序再次运行,警告更少,类型提示更好。

需要删除forClass,因为value 存在:FacesConverter is using both value andforClass, only value will be applied.

原始类型警告将会出现(从 Java SE 5 开始),因为您使用的接口具有您需要满足的泛型(通常在接口名称后表示为 &lt;T&gt;),因此编译器停止在你。

【讨论】:

  • f:selectItem itemValue="#{null}" 确实是个问题。不过,f:selectItem itemValue="" 如何使用 Mojarra 2.2.X 版本仍然存在疑问?
  • 这个issue也跟这个话题link有关。
  • 类似问题,但使用 Primefaces 且没有 JSF2.3(允许泛型,但 2.2 不允许)。因此,如果这解决了您的问题(通过使用 #{null}),您能否将我的答案标记为解决方案?
  • @ZnasMe 可能是 Mojarra 中的 API 更改,他们重新决定并重写了它(更清晰的代码:null != ""(Java 代码中的空字符串)。跨度>
  • 请注意,从转换器中调用 EJB 确实有效,但性能不是很好,因为 每个 id->对象转换都会导致 EJB 调用“昂贵” (通过 CORBA + JPA 调用在两个方向上对数据进行序列化和反序列化)。这可以通过让 Web 应用程序(当然也包括 Swing)使用 JS107 缓存并且转换器通过 CDI 调用支持 bean 的方法来提高(不是由主题启动器询问)。
【解决方案2】:

我在使用 FacesConverter 时遇到了两个问题。

第一个,当我没有使用forClass = MyCustomObject.class 时,我收到了与您完全相同的错误消息。将forClass 属性放在@FacesConverter 注释中解决了这个问题。它也应该为你解决它......

其次,我想这发生在 getAsString 方法中?我在字符串转换方面遇到了另一个问题,我不得不这样做:return myCustomObject.getId().toString() 其中 id 是 Long 类型。之后,我调整了我的 getAsObject 以根据 id 获取 MyCustomObject。也许它与您的问题没有直接关系,但我认为这很重要,因为这是我在编写转换器时经常遇到的问题。

【讨论】:

  • 如果我使用 forClass=MyCustomObject.class 它确实有效,但在我的情况下它不是理想的解决方案。此外,在 getAsString 方法中,字符串在没有强制转换的情况下返回。因此,在@FacesConvertor 上设置value 时的解决方案仍然不起作用。我认为这是 JSF/Mojarra/CDI 集成的问题。
  • 不知何故出了问题,无论是在您的转换器中还是在您的 selectOneMenu 的定义中。您能否发布转换器的代码以及 selectonemenu 中的内容?
  • 我投了赞成票,因为它是一种替代方法(forClass 而不是注释的value 属性),它会在使用 POJO(主要)的任何地方自动注册转换器。但是myPojo.getField().toString() 对我来说看起来有点不对劲,因为getField() 可能会返回null,然后你会在这里得到(著名的)NullPointerException。如我的回答所示,我使用String.valueOf(value.getField());(当您查看方法的源代码时)是空安全的。
  • 有点离题但可以防止 NPE:使用Objects.equals(o1, o2) 而不是o1.equals(o2),因为第一个确实会为您检查null 参考,然后为您调用o1.equals(o2)。跨度>
最近更新 更多