Automatyczne delegowanie wszystkich metod klasy java

Powiedzmy, że mam klasę z wieloma metodami publicznymi:

public class MyClass {

    public void method1() {}
    public void method2() {}
    (...)
    public void methodN() {}

}

Teraz chciałbym utworzyć klasę wrapper , która delegowałaby wszystkie metody do zawiniętej instancji (delegate):

public class WrapperClass extends MyClass  {
    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delagate = delegate;
    }

    public void method1() { delegate.method1(); }
    public void method2() { delegate.method2(); }
    (...)
    public void methodN() { delegate.methodN(); }

}

Teraz, jeśli MyClass ma wiele metod, musiałbym nadpisać każdą z nich, która jest mniej więcej tym samym kodem, który po prostu "deleguje". Zastanawiałem się, czy można zrobić jakąś magię, aby automatycznie wywołać metodę w Javie (więc Klasa wrappera musiałaby powiedzieć " Hej, jeśli wywołujesz metodę na mnie, po prostu przejdź do obiektu delegate i wywołaj tę metodę na nim).

BTW: nie mogę używać dziedziczenia, ponieważ delegat nie jest pod moją kontrolą.Po prostu dostaję jego instancję z innego miejsca (inny przypadek byłby, gdyby MyClass był ostateczny).

Uwaga: nie chcę generowania IDE. Wiem, że mogę to zrobić z Pomocą IntelliJ / Eclipse, ale jestem ciekaw, czy można to zrobić w kodzie.

Jakieś sugestie jak osiągnąć coś takiego? (Uwaga: prawdopodobnie byłbym potrafiłem to zrobić w niektórych językach skryptowych, takich jak php, gdzie mogłem użyć magicznych funkcji php do przechwycenia połączenia).

Author: walkeros, 2015-05-20

7 answers

Być może dynamiczny Proxy java może Ci pomóc. Działa tylko wtedy, gdy konsekwentnie używasz interfejsów. W tym przypadku wywołam interfejs MyInterface i skonfiguruję domyślną implementację:

public class MyClass implements MyInterface {

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

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

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

    public static void main(String[] args) {
        MyClass wrapped = new MyClass();
        wrapped.method1();
        wrapped.method2();
        MyInterface wrapper = WrapperClass.wrap(wrapped);
        wrapper.method1();
        wrapper.method2();
    }

}

Implementacja klasy wrapper wyglądałaby następująco:

public class WrapperClass extends MyClass implements MyInterface, InvocationHandler {

    private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delegate = delegate;
    }

    public static MyInterface wrap(MyClass wrapped) {
        return (MyInterface) Proxy.newProxyInstance(MyClass.class.getClassLoader(), new Class[] { MyInterface.class }, new WrapperClass(wrapped));
    }

    //you may skip this definition, it is only for demonstration
    public void method1() {
        System.out.println("bar");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) {
            return m.invoke(this, args);
        }
        m = findMethod(delegate.getClass(), method);
        if (m != null) {
            return m.invoke(delegate, args);
        }
        return null;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Zauważ, że klasa ta:

  • extends MyClass, Aby dziedziczyć domyślną implementację (zrobiłaby to każda inna)
  • implementuje Invocationhandler, aby proxy mógł wykonać odbicie
  • opcjonalnie zaimplementuj MyInterface (do wzór dekoratora)

To rozwiązanie pozwala nadpisać specjalne metody, ale delegować wszystkie inne. Będzie to działać nawet z podklasami klasy Wrapper.

Zauważ, że metoda findMethod nie uchwyciła jeszcze szczególnych przypadków.

 18
Author: CoronA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-20 10:00:17

To pytanie ma już 6 miesięcy i cudowna odpowiedź @CoronA jest zadowolona i została zaakceptowana przez @walkeros, ale pomyślałem, że coś tu dodam, ponieważ myślę, że można to przesunąć o dodatkowy krok.

Jak omówiono z @CoronA w komentarzach do jego odpowiedzi, zamiast tworzyć i utrzymywać długą listę metod MyClass w WrapperClass (tj. public void methodN() { delegate.methodN(); }), dynamiczne rozwiązanie proxy przenosi to do interfejsu. Problem polega na tym, że nadal musisz tworzyć i utrzymywać długą listę sygnatury dla metod MyClass w interfejsie, co jest być może nieco prostsze, ale nie rozwiązuje całkowicie problemu. Jest to szczególnie ważne, jeśli nie masz dostępu do MyClass w celu poznania wszystkich metod.

Zgodnie z trzy podejścia do dekorowania kodu ,

Dla dłuższych klas programista musi wybrać mniejsze zło: zaimplementuj wiele metod owijania i zachowaj Typ zdobionego obiektu lub utrzymania prostego dekoratora realizacja i poświęcenie zdobiony typ obiektu.

Więc być może jest to oczekiwane ograniczenie wzoru dekoratora.

@Mark-Bramnik daje jednak fascynujące rozwiązanie przy użyciu CGLIB W Interposing na metody klas Java (bez interfejsów) . Udało mi się połączyć to z rozwiązaniem @CoronaA w celu stworzenia wrappera, który może nadpisać poszczególne metody, a następnie przekazać wszystko inne do owiniętego obiektu BEZ wymagającego interfejsu.

Oto MyClass.

public class MyClass {

    public void method1() { System.out.println("This is method 1 - " + this); } 
    public void method2() { System.out.println("This is method 2 - " + this); } 
    public void method3() { System.out.println("This is method 3 - " + this); } 
    public void methodN() { System.out.println("This is method N - " + this); }

}

Oto WrapperClass, które tylko nadpisuje method2(). Jak zobaczysz poniżej, metody nie nadpisane nie są w rzeczywistości przekazywane do delegata, co może być problemem.

public class WrapperClass extends MyClass {

    private MyClass delagate;

    public WrapperClass(MyClass delegate) { this.delagate = delegate; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + delagate);
    }

}

Oto MyInterceptor, który rozszerza MyClass. Wykorzystuje rozwiązanie proxy przy użyciu CGLIB, jak opisuje @Mark-Bramnik. Wykorzystuje również metodę @CononA do określenia, czy wysłać metodę do wrappera (jeśli jest ona nadpisana) lub zawinięty obiekt (jeśli nie jest).

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyInterceptor extends MyClass implements MethodInterceptor {

    private Object realObj;

    public MyInterceptor(Object obj) { this.realObj = obj; }

    @Override
    public void method2() {
        System.out.println("This is overridden method 2 - " + realObj);
    }

    @Override
    public Object intercept(Object arg0, Method method, Object[] objects,
            MethodProxy methodProxy) throws Throwable {
        Method m = findMethod(this.getClass(), method);
        if (m != null) { return m.invoke(this, objects); }
        Object res = method.invoke(realObj, objects);
        return res;
    }

    private Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

}

Oto {[14] } i wyniki, które otrzymasz, jeśli go uruchomisz.

import net.sf.cglib.proxy.Enhancer;

public class Main {

    private static MyClass unwrapped;
    private static WrapperClass wrapped;
    private static MyClass proxified;

    public static void main(String[] args) {
        unwrapped = new MyClass();
        System.out.println(">>> Methods from the unwrapped object:");
        unwrapped.method1();
        unwrapped.method2();
        unwrapped.method3();
        wrapped = new WrapperClass(unwrapped);
        System.out.println(">>> Methods from the wrapped object:");
        wrapped.method1();
        wrapped.method2();
        wrapped.method3();
        proxified = createProxy(unwrapped);
        System.out.println(">>> Methods from the proxy object:");
        proxified.method1();
        proxified.method2();
        proxified.method3();
    }

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T obj) {
        Enhancer e = new Enhancer();
        e.setSuperclass(obj.getClass());
        e.setCallback(new MyInterceptor(obj));
        T proxifiedObj = (T) e.create();
        return proxifiedObj;
    }

}

>>> Methods from the unwrapped object:
This is method 1 - MyClass@e26db62
This is method 2 - MyClass@e26db62
This is method 3 - MyClass@e26db62

>>> Methods from the wrapped object:
This is method 1 - WrapperClass@7b7035c6
This is overridden method 2 - MyClass@e26db62
This is method 3 - WrapperClass@7b7035c6

>>> Methods from the proxy object:
This is method 1 - MyClass@e26db62
This is overridden method 2 - MyClass@e26db62
This is method 3 - MyClass@e26db62

Jak widzisz, uruchamiając metody na wrapped otrzymujesz pakiet dla metod, które nie są nadpisane (np. method1() i method3()). Jednak gdy uruchomisz metody na proxified, wszystkie metody są uruchamiane na owiniętym obiekcie bez konieczności delegowania ich wszystkich w WrapperClass lub umieszczania wszystkich podpisów metod w interfejsie. Dzięki @CoronA i @Mark-Bramnik za to, co wydaje się całkiem fajnym rozwiązaniem tego problemu.

 5
Author: Mark Cramer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:45

Przełącz na Groovy : -)

@CompileStatic
public class WrapperClass extends MyClass  {
    @Delegate private final MyClass delegate;

    public WrapperClass(MyClass delegate) {
        this.delagate = delegate;
    }

    //Done. That's it.

}

Http://mrhaki.blogspot.com/2009/08/groovy-goodness-delegate-to-simplify.html

 4
Author: Snekse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-11 19:10:05

Sprawdź adnotację @ Delegation z Lombok framework: https://projectlombok.org/features/Delegate.html

 2
Author: Vanessa Schissato,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-16 17:08:12

Nie musisz tego robić -- twoja klasa Opakowująca jest podklasą oryginalnej klasy, więc dziedziczy wszystkie jej publicznie dostępne metody -- i jeśli ich nie zaimplementujesz, oryginalna metoda zostanie wywołana.

Nie powinieneś mieć extends Myclass razem z prywatnym MyClass obiektem -- To naprawdę zbędne, i nie mogę wymyślić wzorca projektowego, w którym robienie tego jest właściwe. Twój WrapperClass jest a MyClass, a zatem można po prostu używać własnych pól i metod zamiast wywoływać delegate.

EDIT: W przypadku MyClass bycia final, omijałbyś deklarację willfull, aby nie zezwalać na podklasowanie przez "udawanie" dziedziczenia; nie wyobrażam sobie nikogo chętnego do tego, poza Tobą, który kontroluje WrapperClass; ale, ponieważ kontrolujesz WrapperClass, nie owijanie wszystkiego, czego nie potrzebujesz, jest naprawdę czymś więcej niż opcją-to właściwa rzecz do zrobienia, ponieważ twój obiekt nie jest a MyClass, i powinien zachowywać się jak w przypadku przemyślałeś to.

EDIT właśnie zmieniłeś swoje pytanie na coś zupełnie innego, usuwając MyClass superklasę do twojego WrapperClass; to trochę źle, bo unieważnia wszystkie odpowiedzi udzielone do tej pory. Powinieneś otworzyć inne pytanie.

 1
Author: Marcus Müller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-20 09:07:58

Zdefiniuj metodę w WrapperClass tj. delegate(), która zwraca instancję MyClass

Lub

Możesz użyć reflection, aby to zrobić, ale wywołujący musi przekazać nazwę metody jako argument do metody exposed. I pojawią się komplikacje dotyczące argumentów metody/przeciążonych metod itp.

BTW: nie mogę używać dziedziczenia, ponieważ delegat nie jest pod moją kontrolą.Po prostu dostaję jego instancję z innego miejsca (inny przypadek byłby, gdyby MyClass był finał)

Kod który napisałeś ma public class WrapperClass extends MyClass

W rzeczywistości twoja obecna implementacja WrapperClass jest w rzeczywistości dekoratorem na MyClass
 0
Author: Nitin Dandriyal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-20 09:25:19

Kredyty trafiają do Corony za wskazanie klasy Proxy i InvocationHandler. Opracowałem bardziej użyteczną klasę wielokrotnego użytku w oparciu o jego rozwiązanie, używając generyków: {]}

public class DelegationUtils {

    public static <I> I wrap(Class<I> iface, I wrapped) {
        return wrapInternally(iface, wrapped, new SimpleDecorator(wrapped));
    }

    private static <I> I wrapInternally (Class<I> iface, I wrapped, InvocationHandler handler) {
        return (I) Proxy.newProxyInstance(wrapped.getClass().getClassLoader(), new Class[] { iface }, handler);
    }

    private static class SimpleDecorator<T> implements InvocationHandler {

        private final T delegate;

        private SimpleDecorator(T delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method m = findMethod(delegate.getClass(), method);
            if (m == null) {
                throw new NullPointerException("Found no method " + method + " in delegate: " + delegate);
            }
            return m.invoke(delegate, args);
        }
    }    

    private static Method findMethod(Class<?> clazz, Method method) throws Throwable {
        try {
            return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return null;
        }
    }
}

Przetestuj to:

public class Test {

    public  interface Test {
        public void sayHello ();
    }

    public static class TestImpl implements Test {
        @Override
        public void sayHello() {
            System.out.println("HELLO!");
        }
    }

    public static void main(String[] args) {
        Test proxy = DelegationUtils.wrap(Test.class, new TestImpl());
        proxy.sayHello();
    }
}

Chciałem utworzyć automatyczną klasę delegowania, która wykonuje metody delegata na EDT. Z tą klasą, po prostu tworzysz nową metodę narzędzia, która będzie używać Edtdecoratora, w którym implementacja zawija m.invoke w SwingUtilities.invokeLater.

Jednakże, jeśli się nad tym zastanowię, może warto ponownie rozważyć utworzenie proxy opartego na odbiciu na interfejsie, który mam - może być czystszy i szybszy i bardziej zrozumiały. Ale to możliwe.

 0
Author: Timmos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-15 12:31:32