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.
 

RealHAZZARD

Erfahrenes Mitglied
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?
 

Thomas Darimont

Premium-User
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
 

RealHAZZARD

Erfahrenes Mitglied
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?
 

limago

Erfahrenes Mitglied
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
 

Thomas Darimont

Premium-User
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
 

limago

Erfahrenes Mitglied
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?
 

Thomas Darimont

Premium-User
Hallo,

ein Java DynamicProxy (java.lang.reflect.Proxy) generiert zur Laufzeit Bytecode für eine neue Klasse die die angegebenen Interfaces implementiert und alle Aufrufe an den angegebenen InvocationHandler dispatched. Der daraus resultierende Proxy kann dann über die implementierten Interfaces angesprochen werden (man kann in auf die angegebenen Interfaces casten). Somit ist der Proxy mit allen Methoden ansprechbar die in den Interfaces definiert sind (natürlich werden alle Methoden von java.lang.Object auch dispatched).

Startet man das oben genannte Beispiel (PropertyChangeAwareProxyExample ) mit dem JVM Property:
Code:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
So findet man im classpath-root die folgende generierte Klasse:

Java:
// Decompiled by DJ v3.7.7.81 Copyright 2004 Atanas Neshkov  Date: 27.05.2007 18:05:09
// Home Page : http://members.fortunecity.com/neshkov/dj.html  - Check often for new version!
// Decompiler options: packimports(3) 

import de.tutorials.IDomainObject;
import java.lang.reflect.*;

public final class $Proxy0 extends Proxy
    implements IDomainObject
{

    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int getAnotherProperty()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m6, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void setAnotherProperty(int i)
    {
        try
        {
            super.h.invoke(this, m5, new Object[] {
                Integer.valueOf(i)
            });
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void setProperty(String s)
    {
        try
        {
            super.h.invoke(this, m3, new Object[] {
                s
            });
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String getProperty()
    {
        try
        {
            return (String)super.h.invoke(this, m4, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m6;
    private static Method m5;
    private static Method m0;
    private static Method m3;
    private static Method m4;
    private static Method m2;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m6 = Class.forName("de.tutorials.IDomainObject").getMethod("getAnotherProperty", new Class[0]);
            m5 = Class.forName("de.tutorials.IDomainObject").getMethod("setAnotherProperty", new Class[] {
                Integer.TYPE
            });
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m3 = Class.forName("de.tutorials.IDomainObject").getMethod("setProperty", new Class[] {
                Class.forName("java.lang.String")
            });
            m4 = Class.forName("de.tutorials.IDomainObject").getMethod("getProperty", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}
Gruß Tom
 

Thomas Darimont

Premium-User
Hallo,

das "Umleiten" für fehlende Methoden könnte man Beispielsweise so machen:
Java:
/**
 * 
 */
package de.tutorials;

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

/**
 * @author Tom
 * 
 */
public class CreateDynamicMethodExample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        IFoo foo = (IFoo) Proxy.newProxyInstance(IFoo.class.getClassLoader(),
                new Class[] { IFoo.class },
                new TargetAwareInvocationHandler<IBubu>(new Bubu()));

        foo.operation1();
        foo.operation2();
        foo.operation3();

    }

    static interface IBubu {
        void operation1();

        void operation2();
    }

    static interface IFoo extends IBubu {
        void operation3();
    }

    static class Bubu implements IBubu {
        @Override
        public void operation1() {
            System.out.println("operation1");
        }

        @Override
        public void operation2() {
            System.out.println("operation2");
        }
    }

    static class TargetAwareInvocationHandler<TTargetType> implements
            java.lang.reflect.InvocationHandler {
        TTargetType target;

        public TargetAwareInvocationHandler(TTargetType target) {
            super();
            this.target = target;
        }

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

            if (isMethodDeclaredFor(getTarget().getClass(), method)) {
                return method.invoke(getTarget(), args);
            } else {
                System.out.println("handle missing method: " + method);
                return null;
            }

        }

        private boolean isMethodDeclaredFor(Class<?> clazz, Method method) {
            try {
                return null != clazz.getDeclaredMethod(method.getName(), method
                        .getParameterTypes());
            } catch (NoSuchMethodException e) {
                return false;
            }

        }

        public TTargetType getTarget() {
            return target;
        }

        public void setTarget(TTargetType target) {
            this.target = target;
        }

    }
}
... nur mal so als Spielerei ;-)

Gruß Tom
 

limago

Erfahrenes Mitglied
Schöne Lösung. Mit dynamisch erzeugten Interfaces und per Reflection könnte man damit sicher ein AUTOLOAD hinbekommen. ;)

Wenn ich mal viel Zeit habe versuche ich es...
 

RealHAZZARD

Erfahrenes Mitglied
Hallo,
erstmal danke für eure Hilfe, aber ich mache es jetzt doch mit dem Observer. Denn das hier vorgeschlagene ist zwar richtig tricky aber es mach die Sache auch nicht wirklich einfacher.
Aber das hier war auf jeden Fall nice to know.
THX
 
L

lier99

Aus Design Sicht ist die Proxy Geschichte besser, weil das Domain Objekt unberührt bleibt. Proxies sehen vielleicht "trickig" aus, aber sind heutzutage in der j2ee Welt allgegenwärtig.