簡體   English   中英

自動委托一個 java class 的所有方法

[英]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

編輯:在MyClassfinal的情況下,您將繞過 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM