java - 使用 Java 泛型为实体实现转换器

标签 java spring jsf generics converter

我正在使用 Spring 和 Hibernate 开发 JSF 项目,其中有许多 Converter遵循相同模式的 s:

  • getAsObject接收对象 id 的字符串表示,将其转换为数字,并获取给定种类和给定 id 的实体

  • getAsString接收实体并返回转换为 String 的对象的 ID

代码基本上如下(省略检查):

@ManagedBean(name="myConverter")
@SessionScoped
public class MyConverter implements Converter {
    private MyService myService;

    /* ... */
    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value) {
        int id = Integer.parseInt(value);
        return myService.getById(id);
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) {
        return ((MyEntity)value).getId().toString();
    }
}

鉴于大量 Converter s 完全像这样(当然除了 MyServiceMyEntity 的类型),我想知道是否值得使用单个通用转换器。 泛型本身的实现并不困难,但我不确定声明 Bean 的正确方法。

可能的解决方案如下:

1 - 编写通用实现,我们称之为 MyGenericConverter , 没有任何 Bean 注解

2 - 将特定的转换器广告写成 MyGenericConverter<T> 的子类并根据需要对其进行注释:

@ManagedBean(name="myFooConverter")
@SessionScoped
public class MyFooConverter implements MyGenericConverter<Foo> {
    /* ... */
}

在写这篇文章时,我意识到也许真的不需要泛型,所以也许我可以简单地编写一个实现这两个方法的基类,并根据需要创建子类。

有一些重要的细节需要处理(比如我必须以某种方式抽象 MyService 类)所以我的第一个问题是:值得这么麻烦吗?

如果是这样,还有其他方法吗?

最佳答案

最简单的方法是让您的所有 JPA 实体都从这样的基础实体扩展:

public abstract class BaseEntity<T extends Number> implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract T getId();

    public abstract void setId(T id);

    @Override
    public int hashCode() {
        return (getId() != null) 
            ? (getClass().getSimpleName().hashCode() + getId().hashCode())
            : super.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        return (other != null && getId() != null
                && other.getClass().isAssignableFrom(getClass()) 
                && getClass().isAssignableFrom(other.getClass())) 
            ? getId().equals(((BaseEntity<?>) other).getId())
            : (other == this);
    }

    @Override
    public String toString() {
        return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
    }

}

请注意,拥有一个合适的 equals() 很重要(和 hashCode() ),否则你将面临 Validation Error: Value is not valid . Class#isAssignableFrom()测试是为了避免失败的测试,例如基于 Hibernate 的代理,无需回退到特定于 Hibernate 的 Hibernate#getClass(Object) 辅助方法。

并拥有这样的基础服务(是的,我忽略了您正在使用 Spring 的事实;这只是为了提供基本想法):

@Stateless
public class BaseService {

    @PersistenceContext
    private EntityManager em;

    public BaseEntity<? extends Number> find(Class<BaseEntity<? extends Number>> type, Number id) {
        return em.find(type, id);
    }

}

并按如下方式实现转换器:

@ManagedBean
@ApplicationScoped
@SuppressWarnings({ "rawtypes", "unchecked" }) // We don't care about BaseEntity's actual type here.
public class BaseEntityConverter implements Converter {

    @EJB
    private BaseService baseService;

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null) {
            return "";
        }

        if (modelValue instanceof BaseEntity) {
            Number id = ((BaseEntity) modelValue).getId();
            return (id != null) ? id.toString() : null;
        } else {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
        }
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            Class<?> type = component.getValueExpression("value").getType(context.getELContext());
            return baseService.find((Class<BaseEntity<? extends Number>>) type, Long.valueOf(submittedValue));
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid ID of BaseEntity", submittedValue)), e);
        }
    }

}

请注意,它已注册为 @ManagedBean而不是 @FacesConverter .这个技巧允许您通过例如在转换器中注入(inject)服务。 @EJB .另见 How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter?所以你需要将其引用为 converter="#{baseEntityConverter}"而不是 converter="baseEntityConverter" .

如果您碰巧经常为 UISelectOne 使用此类转换器/ UISelectMany 组件( <h:selectOneMenu> 和 friend ),你可能会发现 OmniFaces SelectItemsConverter 更有用。它根据 <f:selectItems> 中可用的值进行转换而不是每次都进行(可能很昂贵的)数据库调用。

关于java - 使用 Java 泛型为实体实现转换器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17343032/

相关文章:

forms - 如何在 JSF 中定义表单操作?

jsf - View/page.jsf 无法恢复

java - 如何在使用 JSOUP/JAVA 解析时保留原始字符串中的 "

java - Windows JVM 命令行参数上的尾随星号在 cygwin bash shell 中被通配

java - 使用 Java 正则表达式模式解析字符串?

java - 使用 SpringData-MongoDB 将 Java 8 Instant 存储为 BSON 日期

java - Jersey :InjectableProvider 没有被选中 - Spring

java - 如何使用 Spring Repository 根据空/非空值对列进行排序?

jsf - ICEfaces SelectOneMenu ValueChangeListener 不工作

java - 当网络连接失败时,如何处理 Java 单例?