'java Field change listener

So I would really want some way of detecting Field changes of a certain object. I have googled for quite a while but haven't found anything. So basically all I need to know, is when some variable of a object changed. lets take this class for instance:

public class Example{
    String text = "test";

    public static void main(String[] args){
        Example e = new Example();
        e.text = "something else";
    }
}

I would basically want to detect when the 'text' variable has changed. I know many of you would say use getters and setters and such, this is not what I want though.

For a reason I can't easily explain, I need to be able to detect the field change from outside the class. Currently I am thinking of using a second thread that basically just scans a list of objects continuously and use reflection to detect changes that way, but that will obviously make the program heavier than it has to be.

So I hope there is a way of just creating listeners for field changes, does anyone know about libraries/systems/methods to do this?



Solution 1:[1]

Let's bring this back to life :)

There are several approaches:

The first one, which I believe works like since Java 6, is to use Bound Properties inside Java Beans (yes, it also works in JavaSE).

A bound property notifies listeners when its value changes. This has two implications:

The bean class includes addPropertyChangeListener() and removePropertyChangeListener() methods for managing the bean's listeners. When a bound property is changed, the bean sends a PropertyChangeEvent to its registered listeners. PropertyChangeEvent and PropertyChangeListener live in the java.beans package.

The java.beans package also includes a class, PropertyChangeSupport, that takes care of most of the work of bound properties. This handy class keeps track of property listeners and includes a convenience method that fires property change events to all registered listeners.

In the second, you get most of the things for free, its on Java 7 (javafx 2.0), and it's called JavaFX Properties.

You can also add a change listener to be notified when the property's value has changed, as shown in You can also add a change listener to be notified when the property's value has changed.

The downside of the latter is that JPA is not friendly with this, so its a bit of a pain to get it running.

Solution 2:[2]

I can't think of a way to do this without some form of API bloat. But, there are ways you can do it with minor API bloat. I've listed 3 potential solutions below in relative order of complexity (based on my opinion). I think #2 is probably what you'll want though.

  1. I know you don't want them, but please reconsider. If you are defining the class that you want to listen to, this will provide the least API bloat, and best performance. However, I can think of at least two reasons this wouldn't work.

    • You are using a 3rd party library that doesn't provide a listener interface. You could get around this by sub-classing or decorating, but I understand why you wouldn't want to do this as there could potentially be a lot of classes you'd need to do it for.
    • You are writing a 3rd party library, and don't know which fields the client programs will need to listen to (This is what I'm doing today actually). If this is the case, you may need to get more creative.
  2. Write a persistor class that uses reflection to change the field values, then trigger listeners. This will probably be the closest to what you want actually. The code would look like this.

public static void setFieldValue(Object instance, Field field, Object value) throws IllegalArgumentException {
    // Provide some mechanism to get the listeners.
    final Collection<Consumer<Object>> listeners = getListeners();

    // Save the accessible flag before modifying it so that we can clean up after ourselves.
    final boolean accessible = field.isAccessible();
    try {
        // Update accessible flag in case the field is not public
        if (!field.canAccess(instance)) {
            field.setAccessible(true);
        }
        field.set(instance, value);

        // Trigger Listeners
        for (Consumer<Object> listener : listeners) {
            listener.accept(value);
        }
    } finally {
        field.setAccessible(accessible);
    }
}

To make it easier to use, you could instead pass in a String instead of a Field, but be careful about how you retrieve the field by name. instance.getClass().getField(fieldName) will return public fields along the whole hierarchy, while instance.getClass().getDeclaredField(fieldName) will return private fields only on the actual class of instance.

  1. Another method could be to use proxies. This would work well if you are using 3rd party code with well-defined interfaces as a proxy will behave like the class you are creating it off of, but will give you a mechanism to intercept any method calls that would change its state. The implementation would look something like this.
public class ProxyFactory {
    public static <T> T getProxy(T instance) {
        // Provide some mechanism to get the listeners.
        final Collection<Consumer<Object>> listeners = getListeners();
        // Create the Proxy
        return (T) Proxy.newProxyInstance(
                instance.getClass().getClassLoader(),
                instance.getClass().getInterfaces(),
                new ProxyListenerTrigger<T>(instance, listeners));
    }

    private static class ProxyListenerTrigger<T> implements InvocationHandler {

        private final T instance;
        private final Collection<Consumer<Object>> listeners;

        public ProxyListenerTrigger(T instance, Collection<Consumer<Object>> listeners) {
            this.instance = instance;
            this.listeners = listeners;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(instance, args);
            Object value = setterValueOf(method, args);
            if (value != null) {
                for (Consumer<Object> listener : listeners) {
                    listener.accept(value);
                }
            }
            return result;
        }

        private static Object setterValueOf(Method method, Object[] args) {
            // Some way of determining if the method being called is a setter
            if (method.getName().contains("set")) {
                return args[0];
            } else {
                return null;
            }
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Sarmiento
Solution 2 CGravelle