2015-05-09 80 views
1

关于将JavaFX属性支持添加到现有的POJO类中,存在很多问题。这些类的属性可以使用javafx.beans.property.adapter包中的适配器创建。但是,以这种方式创建的属性不会反映使用POJO类的setter方法所做的更改,除非PropertyChangeSupport已添加到POJO类中。完全支持POJO中的JavaFX属性

改变现有的类有时是不可能的,即使是这样,如果有很多类,添加PropertyChangeSupport可能会非常乏味。所以我想分享一个方法来做到这一点,不需要改变现有的类。

回答

1

该解决方案灵感来自Ben Galbraith的article,并使用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); 
} 

接下来,我们将创建该接口的实现。

package com.mycompany.myapp; 

import com.mycompany.myapp.model.BaseEntity; 

import java.beans.PropertyChangeListener; 
import java.beans.PropertyChangeSupport; 

public class ChangeSupportImpl implements ChangeSupport { 
    // Declared transient as there is no need to serialize these fields 
    private transient PropertyChangeSupport propertyChangeSupport; 
    private final transient Object source; 

    public ChangeSupportImpl() { 
     super(); 
     this.source = this; 
    } 

    // Needed for annotation-style aspect 
    public ChangeSupportImpl(final BaseEntity baseEntity) { 
     super(); 
     this.source = baseEntity; 
    } 

    @Override 
    public void addPropertyChangeListener(final PropertyChangeListener listener) { 
     // PropertyChangeSupport is loaded lazily 
     if (this.propertyChangeSupport == null) 
      this.propertyChangeSupport = new PropertyChangeSupport(this.source); 
     this.propertyChangeSupport.addPropertyChangeListener(listener); 
    } 

    @Override 
    public void removePropertyChangeListener(final PropertyChangeListener listener) { 
     if (this.propertyChangeSupport != null) 
      this.propertyChangeSupport.removePropertyChangeListener(listener); 
    } 

    @Override 
    public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener) { 
     // PropertyChangeSupport is loaded lazily 
     if (this.propertyChangeSupport == null) 
      this.propertyChangeSupport = new PropertyChangeSupport(this.source); 
     this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener); 
    } 

    @Override 
    public void removePropertyChangeListener(final String propertyName, final PropertyChangeListener listener) { 
     if (this.propertyChangeSupport != null) 
      this.propertyChangeSupport.removePropertyChangeListener(propertyName, listener); 
    } 

    @Override 
    public void firePropertyChange(final String propertyName, final Object oldValue, final Object newValue) { 
     if (this.propertyChangeSupport != null) 
      this.propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); 
    } 

    @Override 
    public boolean hasListeners(final String propertyName) { 
     return this.propertyChangeSupport != null && (this.propertyChangeSupport.hasListeners(propertyName) 
       || this.propertyChangeSupport.hasListeners(null)); 
    } 
} 

最后,我们将创建一个将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); 
     } 
    } 
}