2015-04-27 3 views
4

У меня есть 2 класса с одним атрибутом, сопоставленным отношением OneToMany/ManyToOne. Проблема в том, что когда я делаю выбор и перехожу к представлению, я хочу проанализировать объект на javascript с помощью Thymeleaf, но он зацикливает бесконечную причину отношения. Мои классам: Класс игрока:Бесконечная рекурсия гиберната и тимелеафа

@Entity 
@Table(name = "player") 
public class Player { 

@Id 
@Column(name = "id") 
@GeneratedValue 
private int id; 

@Column(name = "level") 
private int level; 

@Column(name = "experience") 
private int experience; 

@OneToMany(mappedBy="player", cascade = CascadeType.ALL) 
private List<InventoryItem> inventory; 

// Constructor 
public Player() { 
} 

// Getters & Setters... 
} 

Класс InventoryItem:

@Entity 
@Table(name = "inventory_item") 
public class InventoryItem { 

    @Id 
    @GeneratedValue 
    @Column(name = "id") 
    private int id; 

    @ManyToOne 
    @JoinColumn(name="id_player") 
    private Player player; 


    public InventoryItem() { 
    } 

    //Getters and Setters... 
} 

Затем я передать объект игрока в представлении и console.log его с JavaScript:

<script th:inline="javascript"> 
/*<![CDATA[*/ 
    console.log([[${player}]]); 
/*]]>*/ 
</script> 

И это ошибка: enter image description here

Как я могу предотвратить двунаправленное отношение при анализе на javascript, что-то вроде игнорировать Player atrubute из всех InventoryItems?

ответ

3

Сегодня я столкнулся с этой проблемой, и я думаю, что Thymeleaf не предлагает для этого легкого решения. Вы всегда можете установить ссылки на родителя на null перед тем, как передать его Тимелеафу, но это кажется уродливым.

Исследуя исходный код Тимелеафа, я заметил, что он использует Introspector для получения информации о свойствах, поэтому мы можем скрыть некоторые свойства, реализовав BeanInfo. Самый простой способ сделать это - создать класс в одном пакете вашего компонента, добавив BeanInfo к имени. Вы можете расширить SimpleBeanInfo и реализовать только те методы, которые вас интересуют (в данном случае getPropertyDescriptors).

В вашем примере, вы могли бы сделать:

public class InventoryItemBeanInfo extends SimpleBeanInfo { 
    @Override 
    public PropertyDescriptor[] getPropertyDescriptors() { 
     try { 
      PropertyDescriptor id = new PropertyDescriptor("id", InventoryItem.class);   
      PropertyDescriptor[] descriptors = {id}; 
      return descriptors; 
     } catch (IntrospectionException e) { 
      throw new Error(e.toString()); 
     } 
    } 
} 

Таким образом, Thymeleaf не будет видеть ваше Player собственность, и вы не имели бы бесконечную рекурсию больше.

Недостатком этого решения является то, что вы должны помнить, что каждый раз, когда вы изменяете атрибуты своего компонента InventoryItem, вам нужно перейти к BeanInfo и добавить к нему новый атрибут.

Итак, я придумал другое решение. Это немного сложнее, но как только он настроен, его легче поддерживать.

Начнем с создания аннотации, указывающей, что свойство должно быть скрыто. Давайте назовем это HiddenProperty:

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface HiddenProperty {} 

Сейчас мы реализуем общий BeanInfo класс, который будет инспектировать боб с помощью отражения и найти свойства.Мы также будем проверять существование наших аннотаций и когда он присутствует в пренебрежении метод:

public class HiddenPropertyAwareBeanInfo extends SimpleBeanInfo { 
    private Class<?> beanClass; 
    private PropertyDescriptor[] descriptors; 

    public HiddenPropertyAwareBeanInfo(Class<?> beanClass) { 
     this.beanClass = beanClass; 
    } 

    @Override 
    public PropertyDescriptor[] getPropertyDescriptors() { 
     if(descriptors != null) { 
      return descriptors; 
     } 

     Method[] methodList = beanClass.getMethods(); 
     List<PropertyDescriptor> propDescriptors = new ArrayList<>(); 

     for(Method m : methodList) { 
      if(Modifier.isStatic(m.getModifiers())) { 
       continue; 
      } 

      try { 
       if(m.getParameterCount() == 0 && !m.isAnnotationPresent(HiddenProperty.class)) { 
        if(m.getName().startsWith("get")) { 
         propDescriptors.add(new PropertyDescriptor(m.getName().substring(3), beanClass)); 
        } else if(m.getName().startsWith("is")) { 
         propDescriptors.add(new PropertyDescriptor(m.getName().substring(2), beanClass)); 
        } 
       } 
      } catch(IntrospectionException ex) { 
       continue; 
      } 
     } 

     descriptors = new PropertyDescriptor[propDescriptors.size()]; 
     return propDescriptors.toArray(descriptors); 
    } 
} 

Теперь создайте BeanInfo, который расширяет этот класс:

public class InventoryItemBeanInfo extends HiddenPropertyAwareBeanInfo { 
    public InventoryItemBeanInfo() { 
     super(InventoryItem.class); 
    } 
} 

Наконец, аннотировать методы получения из недвижимость, которую вы хотите скрыть:

@Entity 
@Table(name = "inventory_item") 
public class InventoryItem { 

    @Id 
    @GeneratedValue 
    @Column(name = "id") 
    private int id; 

    @ManyToOne 
    @JoinColumn(name="id_player") 
    private Player player; 


    public InventoryItem() { 
    } 

    @HiddenProperty 
    public Player getPlayer() { 
     return this.player; 
    } 
    //Other getters and setters... 
} 

И у вас оно есть. Теперь вы можете изменить свои свойства столько, сколько хотите, и ваше имущество Player не будет видно Thymeleaf.

Важно отметить, что это решение не соответствует 100% спецификации Java Beans, оно не учитывает наличие индексированных свойств и некоторых других деталей. Вы можете посмотреть Introspectorsource code, чтобы узнать, как они извлекают эту информацию и улучшают это решение.

Но, лично, я думаю, что это должно быть исправлено внутри Тимелеафа. Тимелеаф - невероятно элегантный инструмент, и прибегать к такого рода обходным решениям делает меня грустным.

+0

Я использовал ваше первое предложение и установил ссылку на родителя равным нулю, и это сработало – gary69

Смежные вопросы