[英]Automatically delegating all methods of a java class
假設我有一個帶有許多公共方法的 class:
public class MyClass {
public void method1() {}
public void method2() {}
(...)
public void methodN() {}
}
現在我想創建一個包裝器class,它將所有方法委托給包裝實例(委托):
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(); }
}
現在,如果 MyClass 有很多方法,我將需要重寫它們中的每一個,它們或多或少與只是“委托”的代碼相同。 我想知道是否有可能做一些魔術來自動調用 Java 中的方法(因此包裝器 class 需要說“嘿,如果你對我調用一個方法,只需 go委托object 並在其上調用此方法)。
順便說一句:我不能使用 inheritance 因為委托不受我的控制。我只是從其他地方獲取它的實例(另一種情況是 MyClass 是最終的)。
注意:我不想生成 IDE。 我知道我可以在 IntelliJ/Eclipse 的幫助下做到這一點,但我很好奇這是否可以在代碼中完成。
任何建議如何實現這樣的目標? (注意:我可能可以用一些腳本語言來做到這一點,比如 php,在那里我可以使用 php 魔術函數來攔截調用)。
或許java的動態Proxy
可以幫到你。 它僅在您因此使用接口時才有效。 在這種情況下,我將調用接口MyInterface
並設置一個默認實現:
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();
}
}
包裝類實現看起來像:
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;
}
}
}
注意這個類:
MyClass
,繼承默認實現(任何其他實現)Invocationhandler
,允許代理進行反射MyInterface
(以滿足裝飾器模式)此解決方案允許您覆蓋特殊方法,但委托所有其他方法。 這甚至適用於 Wrapper 類的子類。
請注意, findMethod
方法尚未捕獲特殊情況。
這個問題已經有 6 個月大了,@CoronA 的精彩回答已經得到滿意並被 @walkeros 接受,但我想我會在這里添加一些內容,因為我認為這可以進一步推進。
正如@CoronA 在對他的回答的評論中所討論的那樣,動態代理解決方案public void methodN() { delegate.methodN(); }
在WrapperClass
創建和維護一長串MyClass
方法(即public void methodN() { delegate.methodN(); }
), public void methodN() { delegate.methodN(); }
將其移動到界面。 問題是您仍然必須為接口中的MyClass
方法創建和維護一長串簽名,這可能更簡單一些,但並不能完全解決問題。 如果您無法訪問MyClass
以了解所有方法,則尤其如此。
根據裝飾代碼的三種方法,
對於較長的類,程序員必須選擇兩害相權取其輕:實現許多包裝方法並保留裝飾對象的類型,或者維護簡單的裝飾器實現並犧牲保留裝飾對象類型。
所以也許這是裝飾者模式的預期限制。
但是,@Mark-Bramnik 在Interposing on Java Class Methods (without interfaces) 中使用 CGLIB給出了一個引人入勝的解決方案。 我能夠將其與@CoronaA 的解決方案結合起來,以創建一個包裝器,該包裝器可以覆蓋單個方法,然后將其他所有內容傳遞給被包裝的對象,而無需接口。
這是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); }
}
這是WrapperClass
,它只覆蓋method2()
。 正如您將在下面看到的,未被覆蓋的方法實際上並未傳遞給委托,這可能是一個問題。
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);
}
}
這是擴展MyClass
MyInterceptor
。 它采用了@Mark-Bramnik 所描述的使用 CGLIB 的代理解決方案。 它還使用@CononA 的方法來確定是否將方法發送到包裝器(如果它被覆蓋)或被包裝的對象(如果不是)。
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;
}
}
}
這是Main
以及運行它時得到的結果。
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
如您所見,當您在wrapped
上運行方法時,您將獲得未被覆蓋的方法的包裝器(即method1()
和method3()
)。 但是,當您在proxified
上運行方法時,所有方法都在包裝對象上運行,而不必將它們全部委托給WrapperClass
或將所有方法簽名放在接口中。 感謝@CoronA 和@Mark-Bramnik 為這個問題提供了一個看起來很酷的解決方案。
切換到 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
檢查來自 Lombok 框架的 @Delegate 注釋: https : //projectlombok.org/features/Delegate.html
您不必這樣做——您的 Wrapper 類是原始類的子類,因此它繼承了所有可公開訪問的方法——如果您不實現它們,則將調用原始方法。
您不應該extends Myclass
與私有MyClass
對象一起extends Myclass
——這真的是多余的,我想不出一種設計模式在哪里這樣做是正確的。 你的WrapperClass
是一個MyClass
,因此你可以只使用它自己的字段和方法而不是調用delegate
。
編輯:在MyClass
為final
的情況下,您將繞過 willfull 聲明以不允許通過“偽造”繼承進行子類化; 除了控制WrapperClass
你之外,我想不出還有誰願意這樣做; 但是,由於您可以控制WrapperClass
,因此不包裝您不需要的所有內容實際上不僅僅是一種選擇——這是正確的做法,因為您的對象不是MyClass
,並且應該只表現得像你在心理上考慮過的情況。
編輯你剛剛改變了你的問題,通過將MyClass
超類刪除到你的WrapperClass
來表示完全不同的東西; 這有點糟糕,因為它使迄今為止給出的所有答案無效。 你應該打開另一個問題。
感謝 CoronA 指出 Proxy 和 InvocationHandler 類。 我使用泛型根據他的解決方案制定了一個更可重用的實用程序類:
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;
}
}
}
測試一下:
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();
}
}
我想創建一個自動委托類,在 EDT 上執行被委托人的方法。 使用這個類,您只需創建一個將使用 EDTDecorator 的新實用程序方法,其中實現將m.invoke
包裝在SwingUtilities.invokeLater
。
但是,如果我對此進行反思,我可能想重新考慮為我擁有的每個接口制作一個非基於反射的代理——它可能更干凈、更快,也更容易理解。 但是,這是可能的。
在WrapperClass
定義一個方法,即delegate()
返回MyClass
的實例
或者
您可以使用反射來做到這一點,但調用者必須將方法名稱作為參數傳遞給公開的方法。 並且會出現有關方法參數/重載方法等的復雜情況。
順便說一句:我不能使用繼承,因為委托不在我的控制之下。我只是從其他地方獲取它的實例(另一種情況是如果 MyClass 是最終的)
您發布的代碼具有public class WrapperClass extends MyClass
實際上,您當前的WrapperClass
實現實際上是 MyClass 之上的裝飾器
讓我重新定義一個特定案例的問題。 我想覆蓋jdbc中ResultSet接口的close方法。 我的目標是在結果集的關閉方法中關閉准備好的語句。 我無法訪問在 ResultSet 接口中實現的類 (DelegatingResultSet)。 ResultSet 接口中有很多方法,將它們一一覆蓋並從 ResultSet 對象調用相應的方法是一種解決方案。 對於動態解決方案,我使用了 Dynamic ProxyClasses ( https://docs.oracle.com/javase/1.5.0/docs/guide/reflection/proxy.html )。
// New ResultSet implementation
public class MyResultSet implements InvocationHandler {
ResultSet rs;
PreparedStatement ps;
private Method closeMethod;
public MyResultSet(ResultSet rs, PreparedStatement ps) {
super();
this.rs = rs;
this.ps = ps;
try {
closeMethod = ResultSet.class.getMethod("close",null);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
public void close() {
try {
rs.close();
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static Object newInstance(ResultSet rs, PreparedStatement ps) {
return java.lang.reflect.Proxy.newProxyInstance(rs.getClass().getClassLoader(), rs.getClass().getInterfaces(),
new MyResultSet(rs,ps));
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
Object result = null;
try {
Class declaringClass = m.getDeclaringClass();
if (m.getName().compareTo("close")==0) {
close();
} else {
result = m.invoke(rs, args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
} finally {
}
return result;
}
}
// 如何調用它:
ResultSet prs = (ResultSet) MyResultSet.newInstance(rs,ps);
我非常感謝@CoronA 的回答。 我還查看了@Mark Cramer 的回答,但是,如果我沒有遺漏任何東西,我認為總是至少有兩個“代理”class 的實例,這兩個對象之間存在奇怪的關系。
這一點,連同 cglib 現在已被棄用的事實,促使我尋找一個基於 ByteBuddy 的新實現。
這就是我想出的:
public class MyClass {
public String testMethod() {
return "11111";
}
public String testMethod2() {
return "aaaaa";
}
}
public class MyClassWithDelegate extends MyClass {
private static final Constructor<? extends MyClassWithDelegate> CONSTRUCTOR_WITH_DELEGATE;
static {
Constructor<? extends MyClassWithDelegate> temp = null;
try {
final var instrumentedMyClassWithDelegateType =
new ByteBuddy()
.subclass(MyClassWithDelegate.class)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(MethodInterceptor.class))
.make()
.load(MyClassWithDelegate.class.getClassLoader())
.getLoaded();
temp = instrumentedMyClassWithDelegateType.getConstructor(MyClass.class);
} catch (final Exception e) {
LOGGER.error("Cannot instrument class {}", MyClassWithDelegate.class, e);
}
CONSTRUCTOR_WITH_DELEGATE = temp;
}
public static MyClassWithDelegate getInstanceWithDelegate(final MyClass myClass) {
try {
return CONSTRUCTOR_WITH_DELEGATE.newInstance(myClass);
} catch (final Exception e) {
LOGGER.error("Cannot get instance of {}", MyClassWithDelegate.class, e);
throw new IllegalStateException();
}
}
private final boolean initialized;
private final MyClass delegate;
public MyClassWithDelegate(final MyClass delegate) {
super();
this.delegate = delegate;
this.initialized = true;
}
public String testMethod() {
return "22222";
}
public static class MethodInterceptor {
@RuntimeType
public static Object intercept(@This final MyClassWithDelegate self,
@Origin final Method method,
@AllArguments final Object[] args,
@SuperMethod final Method superMethod) throws Throwable {
if (!self.initialized || method.getDeclaringClass().equals(MyClassWithDelegate.class)) {
return superMethod.invoke(self, args);
} else {
return method.invoke(self.delegate, args);
}
}
}
}
initialized
字段用於防止方法調用super
構造函數在其賦值之前被重定向到委托(在這種情況下這不是問題,但我想創建一個通用的解決方案)。
在MyClassWithDelegate
實例上調用的每個方法都將重定向到委托,除了在MyClassWithDelegate
本身內部聲明的方法。
在此示例中,對MyClassWithDelegate
的實例調用testMethod()
將返回“22222”,而testMethod2()
將返回“aaaaa”。
顯然,只有在調用getInstanceWithDelegate
工廠方法獲得MyClassWithDelegate
的每個實例時,委托才會真正起作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.