自动委派 Java 类的所有方法

2022-09-01 09:38:51

假设我有一个包含许多公共方法的类:

public class MyClass {

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

}

现在我想创建一个包装类,它将所有方法委托给包装的实例(委托):

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中的方法(所以Wrappler类需要说“嘿,如果你对我调用一个方法,就去委托对象并在其上调用此方法)。

顺便说一句:我不能使用继承,因为委托不在我的控制之下。我只是从其他地方获得它的实例(另一种情况是如果MyClass是最终的)。

注意:我不希望生成 IDE。我知道我可以在IntelliJ / Eclipse的帮助下做到这一点,但我很好奇这是否可以在代码中完成。

任何建议如何实现这样的事情?(注意:我可能能够在一些脚本语言(如php)中做到这一点,在那里我可以使用php魔术函数来拦截调用)。


答案 1

也许java的动态可以帮助你。它仅在您因此使用接口时才有效。在这种情况下,我将调用接口并设置一个默认实现:ProxyMyInterface

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;
        }
    }

}

请注意,此类:

  • extends,以继承默认实现(任何其他实现都可以)MyClass
  • 实现 ,以允许代理进行反射Invocationhandler
  • 可选实现(以满足装饰器模式)MyInterface

此解决方案允许您重写特殊方法,但委派所有其他方法。这甚至可以与包装类的子类一起使用。

请注意,该方法尚未捕获特殊情况。findMethod


答案 2

这个问题已经有6个月的历史了,@CoronA的精彩答案已经满足并被@walkeros接受,但我想我会在这里添加一些东西,因为我认为这可以推一个额外的步骤。

正如@CoronA在他的答案的评论中所讨论的那样,动态代理解决方案不必在(即)中创建和维护一长串方法,而是将其移动到接口。问题是,您仍然必须为接口中的方法创建和维护一长串签名,这可能更简单一些,但不能完全解决问题。如果您无权访问以了解所有方法,则尤其如此。MyClassWrapperClasspublic void methodN() { delegate.methodN(); }MyClassMyClass

根据装饰代码的三种方法

对于较长的类,程序员必须选择两种邪恶中的较小者:实现许多包装器方法并保留装饰对象的类型,或者维护简单的装饰器实现并牺牲保留装饰对象类型。

因此,也许这是装饰器模式的预期限制。

然而,@Mark-Bramnik在插入Java类方法(没有接口)时使用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); }

}

这是仅覆盖 .正如您将在下面看到的,未重写的方法实际上没有传递给委托,这可能是一个问题。WrapperClassmethod2()

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

}

这是扩展.它使用@Mark-Bramnik所描述的使用CGLIMB的代理解决方案。它还采用@CononA的方法来确定是将方法发送到包装器(如果它被覆盖)还是将包装对象(如果不是)。MyInterceptorMyClass

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

如您所见,当您运行 方法时,您将获得未重写的方法的包装器(即 和 )。但是,当您在 上运行方法时,所有方法都在包装的对象上运行,而不必将它们全部委托或将所有方法签名放在接口中。感谢@CoronA和@Mark-Bramnik,这似乎是一个非常酷的解决方案。wrappedmethod1()method3()proxifiedWrapperClass