2015-05-09 2 views
1

Существует много вопросов относительно добавления поддержки свойств JavaFX существующим классам POJO. Свойства этих классов могут быть созданы с помощью адаптеров в пакете javafx.beans.property.adapter. Однако свойства, созданные таким образом, не будут отражать изменения, внесенные с использованием методов setter классов POJO, если только PropertyChangeSupport не добавлен в класс POJO.Полная поддержка свойств JavaFX в POJO

Изменение существующих классов иногда невозможно, и даже если это так, добавление PropertyChangeSupport может быть очень утомительным, если у вас много классов. Поэтому я хотел разделить способ сделать это, что не требует изменения существующих классов.

ответ

1

Решение основано на article от Ben Galbraith и использует AspectJ. Это не требует абсолютно никаких изменений в существующих классах моделей. Установка AspectJ выходит за рамки этого руководства, достаточно сказать, что все основные IDE поддерживают его (установка в Eclipse тривиальна).

В этом примере предполагается, что все ваши классы моделей расширяют базовый класс, называемый BaseEntity в этом случае. Если ваша реализация отличается от этого, вам, конечно же, придется адаптировать этот аспект.

Во-первых, мы создадим интерфейс, который определяет методы, необходимые для PropertyChangeSupport.

package com.mycompany.myapp; 

import java.beans.PropertyChangeListener; 

public interface ChangeSupport { 
    // Add listener for all properties 
    public void addPropertyChangeListener(PropertyChangeListener listener); 
    // Remove listener for all properties 
    public void removePropertyChangeListener(PropertyChangeListener listener); 
    // Add listener for specific property 
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener); 
    // Remove listener for specific property 
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener); 
    // Fire change event for specific property 
    public void firePropertyChange(String propertyName, Object oldValue, Object newValue); 
    // Check if property has any listeners attached 
    public boolean hasListeners(String propertyName); 
} 

Далее мы создадим реализацию этого интерфейса.

Наконец, мы создадим аспект, который добавит свойство PropertyChangeSupport в класс BaseEntity. Аспект использует настраиваемый класс ReflectUtils для получения старого значения свойства. Вы можете использовать любую полезную утилиту, которая вам нравится, или простое старое отражение Java (что может повлиять на производительность).

package com.mycompany.myapp; 

import com.mycompany.myapp.model.BaseEntity; 
import com.mycompany.myapp.util.ReflectUtils; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.annotation.DeclareMixin; 

import java.util.Objects; 

@Aspect 
public class BaseEntityObservabilityAspect { 
    @DeclareMixin("com.mycompany.myapp.model.BaseEntity") 
    public static ChangeSupport createChangeSupportImplementation(final BaseEntity baseEntity) { 
     return new ChangeSupportImpl(baseEntity); 
    } 

    // Intercept setters in all BaseEntity objects in order to notify about property change 
    @Around("this(baseEntity) && execution(public void set*(*))") 
    public void firePropertyChange(final BaseEntity baseEntity, 
      final ProceedingJoinPoint joinPoint) throws Throwable { 
     // Get property name from method name 
     final String setterName = joinPoint.getSignature().getName(); 
     final String property = setterName.substring(3, 4).toLowerCase() + setterName.substring(4); 
     final ChangeSupport support = (ChangeSupport)baseEntity; 
     if (support.hasListeners(property)) { 
      // Get old value via reflection 
      final Object oldValue = ReflectUtils.invokeGetter(baseEntity, property); 

      // Proceed with the invocation of the method 
      joinPoint.proceed(); 

      // New value is the first (and only) argument of this method 
      final Object newValue = joinPoint.getArgs()[0]; 
      // Fire only if value actually changed 
      if (!Objects.equals(oldValue, newValue)) 
       support.firePropertyChange(property, oldValue, newValue); 
     } else { 
      // No listeners have been registered with BaseEntity, so there is no need to fire property change event 
      joinPoint.proceed(); 
     } 
    } 
} 

Если по какой-либо причине вы не можете использовать стиль аннотации, то здесь же используется стиль кода AspectJ.

package com.mycompany.myapp; 

import java.util.Objects; 

import com.mycompany.myapp.model.BaseEntity; 
import com.mycompany.myapp.util.ReflectUtils; 

public aspect BaseEntityObservabilityAspect { 
    declare parents: BaseEntity extends ChangeSupportImpl; 

    // Intercept setters in all BaseEntity objects in order to notify about property change 
    void around(final BaseEntity entity, final ChangeSupport support): 
      this(entity) && this(support) && execution(public void BaseEntity+.set*(*)) { 
     // Get property name from method name 
     final String setterName = thisJoinPoint.getSignature().getName(); 
     final String property = setterName.substring(3, 4).toLowerCase() + setterName.substring(4); 
     if (support.hasListeners(property)) { 
      final Object oldValue; 
      try { 
       // Get old value via reflection 
       oldValue = ReflectUtils.invokeGetter(entity, property); 
      } catch (final Throwable e) { 
       // Should not happen 
       proceed(entity, support); 
       return; 
      } 

      // Proceed with the invocation of the method 
      proceed(entity, support); 

      // New value is the first (and only) argument of this method 
      final Object newValue = thisJoinPoint.getArgs()[0]; 
      // Fire only if value actually changed 
      if (!Objects.equals(oldValue, newValue)) 
       support.firePropertyChange(property, oldValue, newValue); 
     } else { 
      // No listeners have been registered with BaseEntity, so there is no need to fire property change event 
      proceed(entity, support); 
     } 
    } 
}