Setter eines Objektes überwachen

RealHAZZARD

Erfahrenes Mitglied
Hallo,

ich habe folgendes Problem:
Es gibt eine Klasse, die eine Entität representiert, also Daten. Die hat nichts weiter, als die Variablen für die Daten und deren Getter und Setter.
Es ist mir nicht möglich diese Klasse zu ändern. Aber ich muss mitkriegen wann die setter dieser Methode aufgerufen WURDEN (also nach dem Aufruf), und welches Feld (Variable) es war.
Jetzt meine Frage: Gibt es mittels Reflection oder Annotations eine Möglichkeit aus einer anderen Klasse (die das Datenobjekt so zu sagen überwachen möchte) mit zu kriegen, wann die Setter des Objektes aufgerufen wurden?

Beispielcode gibts keinen, weil das bisher noch eine Überlegung ist. Und der würde auch nicht so viel helfen, weil es sich ja nur um eine Art DTO handelt, und eine Klasse die dieses Überwachen möchte.

Vielen Dank schon mal.
 
Hallo,

danke für die schnelle Antwort, aber ich habe die Vorgabe mit dem JDK von Sun aus zu kommen. Kann man das auch irgendwie so lösen?
 
Hallo,

so würde ein JDK DynamicProxy basierter Ansatz aussehen ... du bräuchtest dann für deine überwachten Objekte entsprechende Interfaces...

Java:
/**
 * 
 */
package de.tutorials;

public class DomainObject implements IDomainObject {
    String property;
    int anotherProperty;

    /**
     * @param property
     * @param anotherProperty
     */
    public DomainObject(String property, int anotherProperty) {
        super();
        this.property = property;
        this.anotherProperty = anotherProperty;
    }

    /* (non-Javadoc)
     * @see de.tutorials.IDomainObject#getProperty()
     */
    public String getProperty() {
        return property;
    }

    /* (non-Javadoc)
     * @see de.tutorials.IDomainObject#setProperty(java.lang.String)
     */
    public void setProperty(String property) {
        this.property = property;
    }

    /* (non-Javadoc)
     * @see de.tutorials.IDomainObject#getAnotherProperty()
     */
    public int getAnotherProperty() {
        return anotherProperty;
    }

    /* (non-Javadoc)
     * @see de.tutorials.IDomainObject#setAnotherProperty(int)
     */
    public void setAnotherProperty(int anotherProperty) {
        this.anotherProperty = anotherProperty;
    }

    @Override
    public String toString() {
        return "property=" + this.property + " anotherProperty="
                + this.anotherProperty;
    }
}

Java:
package de.tutorials;

public interface IDomainObject {

    /**
     * @return the property
     */
    String getProperty();

    /**
     * @param property
     *            the property to set
     */
    void setProperty(String property);

    /**
     * @return the anotherProperty
     */
    int getAnotherProperty();

    /**
     * @param anotherProperty
     *            the anotherProperty to set
     */
    void setAnotherProperty(int anotherProperty);

}

Java:
/**
 * 
 */
package de.tutorials;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author Thomas.Darimont
 * 
 */
public class PropertyChangeAwareProxyExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IDomainObject proxy = createPropertyChangeAwareProxyFor(new DomainObject(
                "bubu", 123));
        System.out.println(proxy);

        proxy.setProperty("gaga");
        proxy.setAnotherProperty(4711);

        System.out.println(proxy);
    }

    @SuppressWarnings("unchecked")
    private static <TTargetType> TTargetType createPropertyChangeAwareProxyFor(
            TTargetType domainObject) {
        return (TTargetType) Proxy.newProxyInstance(domainObject.getClass()
                .getClassLoader(), domainObject.getClass().getInterfaces(),
                new PropertyChangeAwareInvocationHandler(domainObject));
    }

    static class PropertyChangeAwareInvocationHandler implements
            InvocationHandler {

        Object invocationTarget;

        /**
         * @param invocationTarget
         */
        public PropertyChangeAwareInvocationHandler(Object invocationTarget) {
            super();
            this.invocationTarget = invocationTarget;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {

            String methodName = method.getName();
            
            if (methodName.toLowerCase().startsWith("set")) {
                
                String propertyName = Character.toLowerCase(methodName
                        .charAt(3))
                        + methodName.substring(4);

                Field propertyField = getInvocationTarget().getClass()
                        .getDeclaredField(propertyName);
                
                propertyField.setAccessible(true);
                
                Object oldValue = propertyField.get(getInvocationTarget());
                
                System.out
                        .printf(
                                "Property [%s] is about to change old value: %s, new value: %s\n",
                                propertyName, oldValue, args[0]);

            }
            return method.invoke(invocationTarget, args);
        }

        /**
         * @return the invocationTarget
         */
        public Object getInvocationTarget() {
            return invocationTarget;
        }

        /**
         * @param invocationTarget the invocationTarget to set
         */
        public void setInvocationTarget(Object invocationTarget) {
            this.invocationTarget = invocationTarget;
        }
    }
}

Ausgabe:
Code:
property=bubu anotherProperty=123
Property [property] is about to change old value: bubu, new value: gaga
Property [anotherProperty] is about to change old value: 123, new value: 4711
property=gaga anotherProperty=4711

Gruß Tom
 
danke. Ich werde mich da mal durchschlagen. Sag mal... mein Datenobjektkann kann auch mal so um die 250 Felder haben...Kann man das Interface auch zur laufzeit erstellen? In meinem nicht-Wissen würde ich sagen, dass man die Klasse ja nach den Feldern durchsuchen kann, aber wie macht man aus dem Wissen dann ein Interface?
 
Dem InvokationHandler ist die Zahl der Methoden egal. Du brauchst nur ein Interface für die Klasse. Da das aber bei vielen Klassen in Arbeit ausarten kann, würde ich über ein generisches Datenobjekt nachdenken.

Java:
package de.tutorials;

import java.beans.PropertyChangeEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;

public class GenericDataObjectImpl extends Observable implements GenericDataObject{

	private Map<String, Object> map = new HashMap<String, Object>();
	
	public Object getValue(String name) {
		
		return map.get(name);
	}

	public void setValue(String name, Object value) {
		PropertyChangeEvent event = new PropertyChangeEvent(this, name, map.get(name), value);
		map.put(name, value);
		fireEvent(event);
	}

	private void fireEvent(PropertyChangeEvent event) {
		setChanged();
		notifyObservers(event);
		
	}
	
	

}

Java:
package de.tutorials;

public interface GenericDataObject {
	
	public Object getValue(String name);
	public void setValue(String name, Object value);

}

Toms Lösung ist sicher besser, weil typsicherer. Außerdem kann sein Proxy für alle möglichen Klassen verwendet werden. Insbesondere beim Loggen, Sicherheitschecks, Transaktionkontrolle etc. ist die Lösung super.

Meine ist dafür einfach. Ein Empfänger muss nur Observer implementieren und Du kannst fast alle Tabellen mit nur einem DataObject behandeln.

Gruss
 
Hallo,

Also ein Interface zur Laufzeit zu generieren bringt dir hier nicht viel, da du ja in deinem Anwendungscode gegen das generierte Interface arbeiten müsstest was aber nicht geht da ja die "feste" Implementierung im Code stehen hast.

Also wenn du Domain-Klassen mit so vielen Properties hast (was an sich schon relativ ungewöhnlich und IMHO auch nicht gut ist) , gäbs noch die Möglichkeit via Eclipse (Refactor -> Extract Interface) entsprechende Interfaces aus Domain Klassen zu extrahieren.

Ansonsten gäbs noch die Möglichkeit nur mit Interfaces zu arbeiten, die über einen DynamicProxy die Daten in einer Art Wrapper in einer Map halten. Damit hat man im Code dann immer noch getypten-Zugriff.
schau mal hier:
http://www.tutorials.de/forum/java/250691-dynamisch-zur-laufzeit-getypte-modelle-erzeugen.html

Gruß Tom
 
Ich meinte die Suchfunktion. Vielleicht sollte man vor dem Tippen, die Suchfunktion bemühen. Dann wäre ich auf Deinen Code gestoßen.

btw. Wie macht der Proxy das eigentlich, dass er die Methoden umleitet. Kann man sowas selbst implementieren? In Perl z.B. gibt es eine Methode "AUTOLOAD" die automatisch gerufen wird, wenn es im Object keine korrespondierende Funktion gibt. Der Proxy will ja ein Interface, deswegen ist ein AUTOLOAD nicht implementierbar, oder doch?
 
Zurück