There are quite a few questions on SO regarding adding JavaFX property support to existing POJO classes. Properties for those classes can be created by using adapters in javafx.beans.property.adapter package. However, properties created in such way will not reflect changes made using setter methods of POJO classes, unless PropertyChangeSupport is added to POJO class.
关于向现有POJO类添加JavaFX属性支持,有很多问题。可以使用javafx.beans.property.adapter包中的适配器创建这些类的属性。但是,以这种方式创建的属性不会反映使用POJO类的setter方法所做的更改,除非将PropertyChangeSupport添加到POJO类中。
Changing existing classes is sometimes not possible, and even when it is, adding PropertyChangeSupport can be extremely tedious if you have a lot of classes. So I wanted to share a way to do it which does not require changing existing classes.
有时候不可能改变现有的类,即使是这样,如果你有很多类,添加PropertyChangeSupport可能会非常繁琐。所以我想分享一种不需要更改现有类的方法。
1 个解决方案
#1
The solution is inspired by an article by Ben Galbraith, and uses AspectJ. It requires absolutely no changes to existing model classes. Installing AspectJ is beyond the scope of this tutorial, suffice to say that all major IDEs support it (installing it in Eclipse is trivial).
该解决方案的灵感来自Ben Galbraith的一篇文章,并使用了AspectJ。它绝对不需要对现有模型类进行任何更改。安装AspectJ超出了本教程的范围,足以说所有主要IDE都支持它(在Eclipse中安装它是微不足道的)。
This example assumes that all your model classes extend a base class, called BaseEntity in this case. If your implementation differs from that, you will of course need to adapt the aspect.
此示例假定所有模型类都扩展了一个基类,在本例中称为BaseEntity。如果您的实现与此不同,您当然需要调整方面。
First, we will create an interface which defines methods needed for PropertyChangeSupport.
首先,我们将创建一个定义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);
}
Next, we will create an implementation of that interface.
接下来,我们将创建该接口的实现。
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));
}
}
Finally, we will create an aspect which adds PropertyChangeSupport to BaseEntity class. The aspect uses a custom class ReflectUtils to get the property's old value. You can use any utility you like, or plain old Java reflection (that may affect performance, though).
最后,我们将创建一个方面,将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();
}
}
}
If you cannot use annotation style for some reason, here is the same using AspectJ code style.
如果由于某种原因无法使用注释样式,则使用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);
}
}
}
#1
The solution is inspired by an article by Ben Galbraith, and uses AspectJ. It requires absolutely no changes to existing model classes. Installing AspectJ is beyond the scope of this tutorial, suffice to say that all major IDEs support it (installing it in Eclipse is trivial).
该解决方案的灵感来自Ben Galbraith的一篇文章,并使用了AspectJ。它绝对不需要对现有模型类进行任何更改。安装AspectJ超出了本教程的范围,足以说所有主要IDE都支持它(在Eclipse中安装它是微不足道的)。
This example assumes that all your model classes extend a base class, called BaseEntity in this case. If your implementation differs from that, you will of course need to adapt the aspect.
此示例假定所有模型类都扩展了一个基类,在本例中称为BaseEntity。如果您的实现与此不同,您当然需要调整方面。
First, we will create an interface which defines methods needed for PropertyChangeSupport.
首先,我们将创建一个定义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);
}
Next, we will create an implementation of that interface.
接下来,我们将创建该接口的实现。
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));
}
}
Finally, we will create an aspect which adds PropertyChangeSupport to BaseEntity class. The aspect uses a custom class ReflectUtils to get the property's old value. You can use any utility you like, or plain old Java reflection (that may affect performance, though).
最后,我们将创建一个方面,将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();
}
}
}
If you cannot use annotation style for some reason, here is the same using AspectJ code style.
如果由于某种原因无法使用注释样式,则使用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);
}
}
}