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).
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.
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.
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
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
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.
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
WrapperClass
jest w rzeczywistości dekoratorem na MyClassWarning: 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.
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