Внедрение преобразователей для объектов с Java Generics

Я работаю над проектом JSF с Spring и Hibernate, среди прочего, есть несколько Converter которые следуют одному и тому же шаблону:

Код по существу следующий (проверки опущены):

 @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 , которые точно такие же (за исключением типа MyService и MyEntity конечно), мне было интересно, стоит ли использовать один общий конвертер. Внедрение родового само по себе не сложно, но я не уверен в правильном подходе к объявлению Beans.

Возможным решением является следующее:

1 – Напишите обобщенную реализацию, назовем ее MyGenericConverter без annotations Bean

2 – Напишите конкретный конвертер объявлений подclassа MyGenericConverter и аннотируйте его по мере необходимости:

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

При написании этого я понял, что, возможно, Generic действительно не нужен, поэтому, возможно, я мог бы просто написать базовый class с реализацией двух методов и подclassа по мере необходимости.

Есть несколько нетривиальных деталей, о которых нужно позаботиться (например, о том, что мне пришлось бы MyService то образом MyService class MyService ), поэтому мой первый вопрос: стоит ли хлопот?

И если да, есть ли другие подходы?

Проще всего было бы позволить всем вашим судам JPA перейти от базового объекта следующим образом:

 public abstract class BaseEntity 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() ), иначе вы столкнетесь с ошибкой проверки: значение недействительно . Тест Class#isAssignableFrom() , чтобы избежать неудачных тестов, например, прокси-серверов на основе Hibernate, без необходимости возвращаться к вспомогательному методу Hibernate#getClass(Object) .

И у вас есть базовый сервис (да, я игнорирую тот факт, что вы используете Spring, просто для того, чтобы дать базовую идею):

 @Stateless public class BaseService { @PersistenceContext private EntityManager em; public BaseEntity find(Class> 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>) 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 . Этот трюк позволяет вам вводить услугу в преобразователь, например, @EJB . См. Также Как ввести @EJB, @PersistenceContext, @Inject, @Autowired и т. Д. В @FacesConverter? Поэтому вам нужно ссылаться на него как на converter="#{baseEntityConverter}" вместо converter="baseEntityConverter" .

Если вы используете такой конвертер чаще, чем часто для компонентов UISelectOne / UISelectMany ( и друзей), вы можете найти OmniFaces SelectItemsConverter гораздо полезнее. Он преобразуется на основе значений, доступных в вместо того, чтобы делать (потенциально дорогостоящие) вызовы БД каждый раз.

Вот мое решение с такими соображениями:

  • Я предполагаю, что вас интересует JPA (не Hibernate)
  • Мое решение не требует расширения любого classа и должно работать для любого компонента сущности JPA, это всего лишь простой class, который вы используете, и не требует внедрения какой-либо службы или DAO . Единственное требование заключается в том, что конвертер напрямую зависит от библиотеки JPA, которая может быть не очень элегантной.
  • Он использует вспомогательные методы для сериализации / десериализации идентификатора компонента. Он преобразует только идентификатор сущности объекта и связывает строку с именем classа и идентификатором, сериализованным и преобразованным в base64. Это возможно из-за того, что в jpa идентификаторы объектов должны реализовывать сериализуемое. Реализация этих методов выполняется в java 1.7, но вы можете найти другие реализации для java <1.7 там
 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;

 / **
  * Общий конвертер объектов jpa для jsf
  * 
  * Преобразует экземпляры jpa в строки с помощью этой формы: @ Преобразует из строк в экземпляры, поиск по id в
  * firebase database
  * 
  * Это возможно благодаря тому, что jpa требует, чтобы все идентификаторы объектов
  * реализовать сериализуемый
  * 
  * Требуется: - вы должны предоставить экземпляр с именем "entityManagerFactory"
  * injected - Не забудьте реализовать equals и hashCode во всей вашей сущности
  * classы !!
  * 
  * /
 @ManagedBean
 @RequestScoped
 публичный class EntityConverter реализует Конвертер {

     private static final char CHARACTER_SEPARATOR = '@';

     @ManagedProperty (value = "# {entityManagerFactory}")
     частный EntityManagerFactory entityManagerFactory;

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

     private static final Строка empty = "";

     @Override
     public Object getAsObject (контекст FacesContext, UIComponent c, String value) {
         if (value == null || value.isEmpty ()) {
             return null;
         }

         int index = value.indexOf (CHARACTER_SEPARATOR);
         String clazz = значение.substring (0, index);
         String idBase64String = value.substring (index + 1, value.length ());
 EntityManager entityManager = null;
         пытаться {
             Класс entityClazz = Class.forName (clazz);
             Object id = convertFromBase64String (idBase64String);

         entityManager = entityManagerFactory.createEntityManager ();
         Объект object = entityManager.find (entityClazz, id);

             возвращаемый объект;

         } catch (ClassNotFoundException e) {
             throw new ConverterException («Jpa entity not found» + clazz, e);
         } catch (IOException e) {
             throw new ConverterException («Не удалось десериализовать id classа jpa» + clazz, e);
         }в конце концов{
         если (EntityManager! = NULL) {
             entityManager.close ();  
         }
     }

     }

     @Override
     public String getAsString (контекст FacesContext, UIComponent c, значение Object) {
         if (значение == null) {
             return empty;
         }
         String clazz = value.getClass (). GetName ();
         String idBase64String;
         пытаться {
             idBase64String = convertToBase64String (entityManagerFactory.getPersistenceUnitUtil (). getIdentifier (значение));
         } catch (IOException e) {
             throw new ConverterException («Невозможно сериализовать идентификатор для classа» + clazz, e);
         }

         return clazz + CHARACTER_SEPARATOR + idBase64String;
     }

     // МЕТОДЫ УТИЛИТЫ, (Могут быть реорганизованы, переместив их в другое место)

     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 (Объект объекта) throws IOException {
         try (ByteArrayOutputStream bos = new ByteArrayOutputStream (); ObjectOutput out = new ObjectOutputStream (bos)) {
             out.writeObject (объект);
             return bos.toByteArray ();
         }
     }

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

 }

Используйте его как другой конвертер с

  
  • Весовые данные JPA-запрос с параметрическими свойствами
  • Могу ли я установить TTL для @Cacheable
  • Может ли Spring Data REST использовать QueryDSL для выполнения более сложных запросов?
  • java.lang.NoSuchFieldError: INSTANCE
  • Spring JSF-интеграция: как внедрить компонент Spring / службу в управляемый bean-компонент JSF?
  • динамически изменять источник данных Spring
  • Обычная аутентификация для REST API с использованием Spring restTemplate
  • Spring REST Service: как настроить удаление нулевых объектов в json-ответе
  • Как обрабатывать MaxUploadSizeExceededException
  • Роль / цель ContextLoaderListener весной?
  • Получить список объектов JSON с Spring RestTemplate
  • Давайте будем гением компьютера.