2013-06-27 51 views
6

tôi đang làm việc trên dự án JSF với Spring và Hibernate mà trong số những thứ khác có một số Converter s mà làm theo cùng một khuôn mẫu:Thực hiện chuyển đổi cho các đối tượng với Java Generics

  • getAsObject nhận chuỗi đại diện của đối tượng id, chuyển nó đến một con số, và lấy đơn vị của các loại nhất định và id cho

  • getAsString nhận và thực thể và trả về id của đối tượng chuyển đổi sang String

Mã này về cơ bản là những gì sau (kiểm tra bỏ qua):

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

Với số lượng lớn các Converter s mà là chính xác như thế này (trừ các loại MyServiceMyEntity tất nhiên), tôi đã tự hỏi nếu nó có giá trị bằng cách sử dụng một bộ chuyển đổi chung duy nhất. Việc thực hiện chung chung không phải là khó khăn, nhưng tôi không chắc chắn về cách tiếp cận đúng để khai báo Đậu.

Một giải pháp khả thi như sau:

1 - Viết bài thi chung, chúng ta hãy gọi nó MyGenericConverter, mà không cần bất kỳ Bean chú thích

2 - Viết bài quảng cáo chuyển đổi cụ thể một lớp con của MyGenericConverter<T> và chú thích nó như là cần thiết:

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

Trong khi viết điều này, tôi có thể chỉ cần viết một lớp cơ sở với việc triển khai hai phương pháp, một d subclass khi cần thiết.

Có một vài chi tiết không quan trọng cần phải được quan tâm (như thực tế là tôi phải trừu tượng lớp MyService theo một cách nào đó) vì vậy câu hỏi đầu tiên của tôi là: liệu nó có đáng giá không?

Và nếu có, có cách tiếp cận nào khác không?

Trả lời

15

dễ nhất sẽ là để cho tất cả các thực JPA của bạn mở rộng từ một tổ chức cơ sở như thế này:

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

} 

Lưu ý rằng điều quan trọng là phải có một hợp equals() (và hashCode()), nếu không bạn sẽ phải đối mặt Validation Error: Value is not valid. Các thử nghiệm Class#isAssignableFrom() là để tránh các thử nghiệm không thành công, ví dụ: Các proxy dựa trên Hibernate mà không cần phải quay trở lại phương thức trợ giúp Hibernate#getClass(Object) Hibernate cụ thể.

Và có một dịch vụ cơ sở như thế này (có, tôi bỏ qua một thực tế rằng bạn đang sử dụng Spring, nó chỉ để cung cấp cho các ý tưởng cơ bản):

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

} 

Và thực hiện chuyển đổi như sau:

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

} 

Lưu ý rằng nó đã được đăng ký là @ManagedBean thay vì @FacesConverter. Thủ thuật này cho phép bạn tiêm một dịch vụ trong trình chuyển đổi thông qua ví dụ:@EJB. Xem thêm How to inject @EJB, @PersistenceContext, @Inject, @Autowired, etc in @FacesConverter? Vì vậy, bạn cần tham khảo nó là converter="#{baseEntityConverter}" thay vì converter="baseEntityConverter".

Nếu bạn tình cờ sử dụng một chuyển đổi như vậy hơn thường cho UISelectOne/UISelectMany linh kiện (<h:selectOneMenu> và bạn bè), bạn có thể tìm OmniFacesSelectItemsConverter nhiều hơn nữa hữu ích. Nó chuyển đổi dựa trên các giá trị có sẵn trong <f:selectItems> thay vì thực hiện (có khả năng đắt tiền) DB gọi mọi lúc.

+1

Sử dụng "id" tài sản là không được khuyến nghị bằng phương thức equals và hashCode: Chúng khuyên bạn nên thực hiện equals() và hashCode() bằng cách sử dụng phương thức Business keyality: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes -equalshashcode.html Và làm thế nào về id tổng hợp? –

0

Đây là giải pháp của tôi với cân nhắc này:

  • tôi asume bạn quan tâm đến JPA (không Hibernate)
  • Giải pháp của tôi không không yêu cầu để mở rộng bất kỳ lớp và nên làm việc cho bất kỳ thực thể JPA đậu, nó chỉ là một lớp đơn giản mà bạn sử dụng, cũng không yêu cầu thực hiện bất kỳ dịch vụ nào hoặc DAO. Yêu cầu duy nhất là bộ chuyển đổi trực tiếp phụ thuộc vào thư viện JPA có thể không rất thanh lịch.
  • Nó sử dụng các phương pháp phụ trợ để tuần tự hóa/deserializing id của đậu. Nó chỉ chuyển đổi id của bean thực thể và hợp chất chuỗi với tên lớp và id được tuần tự hóa và chuyển đổi thành base64. Điều này có thể do thực tế là trong jpa, các id của các thực thể phải triển khai có thể tuần tự hóa được. Việc thực hiện phương pháp này là trong java 1.7, nhưng bạn có thể tìm thấy một hiện thực cho java < 1,7 đằng kia
 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.ObjectInput; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutput; 
import java.io.ObjectOutputStream; 

import javax.faces.bean.ManagedBean; 
import javax.faces.bean.ManagedProperty; 
import javax.faces.bean.RequestScoped; 
import javax.faces.component.UIComponent; 
import javax.faces.context.FacesContext; 
import javax.faces.convert.Converter; 
import javax.faces.convert.ConverterException; 
import javax.persistence.EntityManagerFactory; 

/** 
* Generic converter of jpa entities for jsf 
* 
* Converts the jpa instances to strings with this form: @ Converts from strings to instances searching by id in 
* database 
* 
* It is possible thanks to the fact that jpa requires all entity ids to 
* implement serializable 
* 
* Requires: - You must provide instance with name "entityManagerFactory" to be 
* injected - Remember to implement equals and hashCode in all your entity 
* classes !! 
* 
*/ 
@ManagedBean 
@RequestScoped 
public class EntityConverter implements Converter { 

    private static final char CHARACTER_SEPARATOR = '@'; 

    @ManagedProperty(value = "#{entityManagerFactory}") 
    private EntityManagerFactory entityManagerFactory; 

    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { 
     this.entityManagerFactory = entityManagerFactory; 
    } 

    private static final String empty = ""; 

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

     int index = value.indexOf(CHARACTER_SEPARATOR); 
     String clazz = value.substring(0, index); 
     String idBase64String = value.substring(index + 1, value.length()); 
EntityManager entityManager=null; 
     try { 
      Class entityClazz = Class.forName(clazz); 
      Object id = convertFromBase64String(idBase64String); 

     entityManager = entityManagerFactory.createEntityManager(); 
     Object object = entityManager.find(entityClazz, id); 

      return object; 

     } catch (ClassNotFoundException e) { 
      throw new ConverterException("Jpa entity not found " + clazz, e); 
     } catch (IOException e) { 
      throw new ConverterException("Could not deserialize id of jpa class " + clazz, e); 
     }finally{ 
     if(entityManager!=null){ 
      entityManager.close(); 
     } 
    } 

    } 

    @Override 
    public String getAsString(FacesContext context, UIComponent c, Object value) { 
     if (value == null) { 
      return empty; 
     } 
     String clazz = value.getClass().getName(); 
     String idBase64String; 
     try { 
      idBase64String = convertToBase64String(entityManagerFactory.getPersistenceUnitUtil().getIdentifier(value)); 
     } catch (IOException e) { 
      throw new ConverterException("Could not serialize id for the class " + clazz, e); 
     } 

     return clazz + CHARACTER_SEPARATOR + idBase64String; 
    } 

    // UTILITY METHODS, (Could be refactored moving it to another place) 

    public static String convertToBase64String(Object o) throws IOException { 
     return javax.xml.bind.DatatypeConverter.printBase64Binary(convertToBytes(o)); 
    } 

    public static Object convertFromBase64String(String str) throws IOException, ClassNotFoundException { 
     return convertFromBytes(javax.xml.bind.DatatypeConverter.parseBase64Binary(str)); 
    } 

    public static byte[] convertToBytes(Object object) throws IOException { 
     try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutput out = new ObjectOutputStream(bos)) { 
      out.writeObject(object); 
      return bos.toByteArray(); 
     } 
    } 

    public static Object convertFromBytes(byte[] bytes) throws IOException, ClassNotFoundException { 
     try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes); ObjectInput in = new ObjectInputStream(bis)) { 
      return in.readObject(); 
     } 
    } 

} 

Sử dụng nó như trình chuyển đổi khác với

<h:selectOneMenu converter="#{entityConverter}" ...