Alternatywy dla java.lang.reflect.Proxy do tworzenia proxy klas abstrakcyjnych (zamiast interfejsów)

89

Zgodnie z dokumentacją :

[ java.lang.reflect.] Proxyzapewnia statyczne metody tworzenia dynamicznych klas i instancji proxy, a także stanowi nadklasę wszystkich dynamicznych klas proxy tworzonych przez te metody.

newProxyMethodMetoda (odpowiedzialny za generowanie dynamicznych proxy) ma następujący podpis:

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

Niestety, uniemożliwia to generowanie dynamicznego serwera proxy, który rozszerza określoną klasę abstrakcyjną (zamiast implementować określone interfejsy). Ma to sens, biorąc pod uwagę, że java.lang.reflect.Proxyjest to „nadklasa wszystkich dynamicznych serwerów proxy”, zapobiegając w ten sposób innej klasie bycia nadklasą.

Czy w związku z tym istnieją alternatywy, java.lang.reflect.Proxyktóre mogą generować dynamiczne serwery proxy, które dziedziczą z określonej klasy abstrakcyjnej, przekierowując wszystkie wywołania metod abstrakcyjnych do modułu obsługi wywołań?

Na przykład załóżmy, że mam klasę abstrakcyjną Dog:

public abstract class Dog {

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

    public abstract void fetch();

}

Czy jest klasa, która pozwala mi wykonywać następujące czynności?

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
źródło

Odpowiedzi:

123

Można to zrobić za pomocą Javassist (zobacz ProxyFactory) lub CGLIB .

Przykład Adama przy użyciu Javassist:

Ja (Adam Paynter) napisałem ten kod używając Javassist:

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();

Który generuje ten wynik:

Wątek!
Obsługa publicznego abstrakcyjnego void mock.Dog.fetch () za pośrednictwem modułu obsługi metody
axtavt
źródło
10
+1: Dokładnie to, czego potrzebuję! Poprawię Twoją odpowiedź za pomocą mojego przykładowego kodu.
Adam Paynter,
proxyFactory.setHandler()jest przestarzałe. Proszę użyć proxy.setHandler.
AlikElzin-kilaka
@axtavt czy obiekt „Dog” jest implementacją lub interfejsem w powyższym kodzie?
stackoverflow
-7

W takim przypadku możesz mieć program obsługi proxy, który przekieruje wywołania do istniejących metod Twojej klasy abstrakcyjnej.

Oczywiście będziesz musiał to zakodować, jednak jest to dość proste. Aby utworzyć proxy, musisz dać mu plik InvocationHandler. Będziesz wtedy musiał tylko sprawdzić typ metody w invoke(..)metodzie obsługi wywołania. Ale uwaga: będziesz musiał porównać typ metody z podstawowym obiektem powiązanym z twoim programem obsługi, a nie z zadeklarowanym typem twojej klasy abstrakcyjnej.

Jeśli weźmiemy jako przykład twoją klasę psa, metoda invoke twojego programu obsługi wywołań może wyglądać następująco (z istniejącą skojarzoną podklasą psa o nazwie… no… 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 ?
    }
}

Jest jednak coś, co każe mi się zastanawiać: mówiłem o dogprzedmiocie. Ale ponieważ klasa Dog jest abstrakcyjna, nie możesz tworzyć instancji, więc masz istniejące podklasy. Ponadto, gdy rygorystyczna inspekcja kodu źródłowego proxy ujawnia, możesz odkryć (na Proxy.java:362), że nie jest możliwe utworzenie proxy dla obiektu klasy, który nie reprezentuje interfejsu).

Tak więc, pomijając rzeczywistość , to, co chcesz zrobić, jest całkowicie możliwe.

Riduidel
źródło
1
Proszę o wyrozumiałość, gdy spróbuję zrozumieć Twoją odpowiedź ... W moim przypadku chcę, aby klasa proxy (generowana w czasie wykonywania) była podklasą Dog(na przykład nie piszę wyraźnie Poodleklasy, która implementuje fetch()). Dlatego nie ma dogzmiennej, na której można wywołać metody ... Przepraszam, jeśli się mylę, będę musiał przemyśleć to trochę więcej.
Adam Paynter
1
@Adam - nie możesz dynamicznie tworzyć podklas w czasie wykonywania bez manipulacji kodem bajtowym (myślę, że CGLib robi coś takiego). Krótka odpowiedź jest taka, że ​​dynamiczne serwery proxy obsługują interfejsy, ale nie klasy abstrakcyjne, ponieważ są to bardzo różne koncepcje. Niemożliwe jest wymyślenie sposobu na dynamiczne proxy klas abstrakcyjnych w rozsądny sposób.
Andrzej Doyle
1
@Andrzej: Rozumiem, że to, o co proszę, wymaga manipulacji kodem bajtowym (tak naprawdę napisałem już rozwiązanie mojego problemu z wykorzystaniem ASM). Rozumiem też, że dynamiczne serwery proxy Javy obsługują tylko interfejsy. Być może moje pytanie nie było do końca jasne - pytam, czy jest jakaś inna klasa (to znaczy coś innego niż java.lang.reflect.Proxy), która robi to, czego potrzebuję.
Adam Paynter,
2
Cóż, mówiąc krótko… nie (przynajmniej w standardowych klasach Javy). Używając manipulacji kodem bajtowym, niebo jest granicą!
Riduidel
9
Głosowałem w dół, ponieważ tak naprawdę nie jest to odpowiedź na pytanie. OP stwierdził, że chce proxy klasy, a nie interfejsu, i zdaje sobie sprawę, że nie jest to możliwe w przypadku java.lang.reflect.Proxy. Po prostu powtarzasz ten fakt i nie oferujesz innego rozwiązania.
jcsahnwaldt Przywróć Monikę