Alternativen zu java.lang.reflect.Proxy zum Erstellen von Proxys abstrakter Klassen (anstelle von Schnittstellen)

87

Laut Dokumentation :

[ java.lang.reflect.] Proxybietet statische Methoden zum Erstellen dynamischer Proxyklassen und -instanzen und ist außerdem die Oberklasse aller dynamischen Proxyklassen, die mit diesen Methoden erstellt wurden.

Die newProxyMethodMethode (die für die Generierung der dynamischen Proxys verantwortlich ist) hat die folgende Signatur:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
                             throws IllegalArgumentException

Leider verhindert man von einem dynamischen Proxy erzeugen , die sich eine bestimmte abstrakte Klasse ( und nicht der Umsetzung spezifische Schnittstellen). Dies ist sinnvoll, wenn java.lang.reflect.Proxyman bedenkt, dass es sich um die "Oberklasse aller dynamischen Proxys" handelt, wodurch verhindert wird, dass eine andere Klasse die Oberklasse ist.

Gibt es daher Alternativen dazu java.lang.reflect.Proxy, die dynamische Proxys generieren können, die von einer bestimmten abstrakten Klasse erben und alle Aufrufe der abstrakten Methoden an den Aufrufhandler umleiten ?

Angenommen, ich habe eine abstrakte Klasse Dog:

public abstract class Dog {

    public void bark() {
        System.out.println("Woof!");
    }

    public abstract void fetch();

}

Gibt es eine Klasse, in der ich Folgendes tun kann?

Dog dog = SomeOtherProxy.newProxyInstance(classLoader, Dog.class, h);

dog.fetch(); // Will be handled by the invocation handler
dog.bark();  // Will NOT be handled by the invocation handler
Adam Paynter
quelle

Antworten:

121

Dies kann mit Javassist (siehe ProxyFactory) oder CGLIB erfolgen .

Adams Beispiel mit Javassist:

Ich (Adam Paynter) habe diesen Code mit Javassist geschrieben:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Dog.class);
factory.setFilter(
    new MethodFilter() {
        @Override
        public boolean isHandled(Method method) {
            return Modifier.isAbstract(method.getModifiers());
        }
    }
);

MethodHandler handler = new MethodHandler() {
    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        System.out.println("Handling " + thisMethod + " via the method handler");
        return null;
    }
};

Dog dog = (Dog) factory.create(new Class<?>[0], new Object[0], handler);
dog.bark();
dog.fetch();

Welches erzeugt diese Ausgabe:

Schuss!
Behandlung von public abstract void mock.Dog.fetch () über den Methodenhandler
axtavt
quelle
10
+1: Genau das was ich brauche! Ich werde Ihre Antwort mit meinem Beispielcode bearbeiten.
Adam Paynter
proxyFactory.setHandler()ist veraltet. Bitte benutzen proxy.setHandler.
AlikElzin-Kilaka
@axtavt ist das Objekt "Dog" eine Implementierung oder eine Schnittstelle im obigen Code?
Stackoverflow
-7

In einem solchen Fall können Sie einen Proxy-Handler verwenden, der Aufrufe an vorhandene Methoden Ihrer abstrakten Klasse umleitet.

Sie müssen es natürlich codieren, aber es ist ganz einfach. Um Ihren Proxy zu erstellen, müssen Sie ihm einen geben InvocationHandler. Sie müssen dann nur noch den Methodentyp in der invoke(..)Methode Ihres Aufrufhandlers überprüfen . Beachten Sie jedoch, dass Sie den Methodentyp anhand des zugrunde liegenden Objekts überprüfen müssen, das Ihrem Handler zugeordnet ist, und nicht anhand des deklarierten Typs Ihrer abstrakten Klasse.

Wenn ich als Beispiel Ihren Hund Klasse nehmen, Ihr Aufruf Handler Aufrufmethode kann wie folgt aussehen (mit einer vorhandenen zugehörigem Hund Unterklasse genannt .. na ja ... dog)

public void invoke(Object proxy, Method method, Object[] args) {
    if(!Modifier.isAbstract(method.getModifiers())) {
        method.invoke(dog, args); // with the correct exception handling
    } else {
        // what can we do with abstract methods ?
    }
}

Es gibt jedoch etwas, das mich immer wieder wundert: Ich habe über ein dogObjekt gesprochen. Da die Dog-Klasse jedoch abstrakt ist, können Sie keine Instanzen erstellen, sodass vorhandene Unterklassen vorhanden sind. Wie eine strenge Überprüfung des Proxy-Quellcodes zeigt, können Sie (unter Proxy.java:362) feststellen, dass es nicht möglich ist, einen Proxy für ein Klassenobjekt zu erstellen, das keine Schnittstelle darstellt.

Abgesehen von der Realität ist das, was Sie tun möchten, durchaus möglich.

Riduidel
quelle
1
Bitte nehmen Sie Kontakt mit mir auf, während ich versuche, Ihre Antwort zu verstehen ... In meinem speziellen Fall möchte ich, dass die Proxy-Klasse (zur Laufzeit generiert) die Unterklasse von ist Dog(zum Beispiel schreibe ich nicht explizit eine PoodleKlasse, die implementiert fetch()). Daher gibt es keine dogVariable, mit der die Methoden aufgerufen werden können ... Entschuldigung, wenn ich verwirrend bin, muss ich mir das etwas genauer überlegen.
Adam Paynter
1
@Adam - Sie können zur Laufzeit keine Unterklassen dynamisch erstellen, ohne Bytecode-Manipulationen vorzunehmen (CGLib macht meiner Meinung nach so etwas). Die kurze Antwort lautet, dass dynamische Proxys Schnittstellen unterstützen, jedoch keine abstrakten Klassen, da es sich bei beiden um sehr unterschiedliche Konzepte handelt. Es ist fast unmöglich, sich eine Möglichkeit vorzustellen, abstrakte Klassen auf vernünftige Weise dynamisch zu vertreten.
Andrzej Doyle
1
@Andrzej: Ich verstehe, dass das, wonach ich frage, eine Bytecode-Manipulation erfordert (tatsächlich habe ich bereits eine Lösung für mein Problem mit ASM geschrieben). Ich verstehe auch, dass die dynamischen Proxys von Java nur Schnittstellen unterstützen. Vielleicht war meine Frage nicht ganz klar - ich frage, ob es eine andere Klasse (dh etwas anderes als java.lang.reflect.Proxy) gibt, die das tut, was ich brauche.
Adam Paynter
2
Um es kurz zu machen ... nein (zumindest in Standard-Java-Klassen). Mit der Bytecode-Manipulation ist der Himmel die Grenze!
Riduidel
9
Ich habe abgelehnt, weil es nicht wirklich eine Antwort auf die Frage ist. OP gab an, dass er eine Klasse und keine Schnittstelle als Proxy verwenden möchte, und ist sich bewusst, dass dies mit java.lang.reflect.Proxy nicht möglich ist. Sie wiederholen diese Tatsache einfach und bieten keine andere Lösung an.
jcsahnwaldt Reinstate Monica