自动委托java类的所有方法

wal*_*ros 21 java reflection delegates

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

public class MyClass {

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

}
Run Code Online (Sandbox Code Playgroud)

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

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

}
Run Code Online (Sandbox Code Playgroud)

现在,如果MyClass有很多方法,我需要覆盖它们中的每一个,这或多或少是相同的代码,只是"委托".我想知道是否有可能做一些魔法来自动调用Java中的方法(所以Wrapper类需要说"嘿,如果你在我身上调用一个方法,只需去委托对象并在其上调用此方法)".

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

注意:我不想生成IDE.我知道我可以在IntelliJ/Eclipse的帮助下完成它,但我很好奇是否可以在代码中完成.

有什么建议如何实现这样的事情?(注意:我可能会在某些脚本语言中执行此操作,例如php,我可以使用php magic功能来拦截调用).

Cor*_*onA 22

也许Proxyjava 的动态可以帮到你.它只适用于您因此使用接口.在这种情况下,我将调用接口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();
    }

}
Run Code Online (Sandbox Code Playgroud)

包装类实现如下所示:

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

}
Run Code Online (Sandbox Code Playgroud)

注意这个类:

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

此解决方案允许您覆盖特殊方法,但可以委派所有其他方法.这甚至可以用于Wrapper类的子类.

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


Van*_*ato 6

检查来自 Lombok 框架的 @Delegate 注释:https : //projectlombok.org/features/Delegate.html

  • 如果你不能使用 Groovy,这将是我的答案,但是,你必须小心,因为 `@Delegate` 在 `v1.14` 中被降级为一个实验性功能。 https://projectlombok.org/features/experimental/all https ://stackoverflow.com/a/12807937/378151 (3认同)

Mar*_*mer 5

这个问题已经有6个月了,@ CoronA的精彩答案已经得到了@walkeros的满意和接受,但我想我会在这里添加一些内容,因为我认为这可以推得更多一步.

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

根据三种装饰代码的方法,

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

所以这可能是装饰模式的预期限制.

然而,@ 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); }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

这是MyInterceptor扩展MyClass.它采用了使用CGLIB的代理解决方案,如@ Mark-Bramnik所述.它还使用@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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这是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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,当您运行方法时,wrapped您将获得未被覆盖的方法的包装(即method1()method3()).proxified但是,当您运行方法时,所有方法都在包装对象上运行,而不必将它们全部委托给所有WrapperClass方法签名或将所有方法签名放在接口中.感谢@CoronA和@ Mark-Bramnik对这个问题的一个非常酷的解决方案.

  • 我决定在`unwrapped`,`wrapped`和`proxified`上运行`methodN()`100万次.仍然不确定这是否是一个公平的测试,但结果分别是0毫秒,4毫秒和1485毫秒.看起来字节码生成库可能会有一些显着的开销.但它的确有效! (2认同)

Sne*_*kse 5

切换到Groovy :-)

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

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

    //Done. That's it.

}
Run Code Online (Sandbox Code Playgroud)

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

  • 我想我已经说过,我知道可以在脚本语言中完成,而问题是关于Java的。 (2认同)
  • 嗯 我取-1。如果在构建时将其编译为.class文件,那它真的是脚本语言吗?如果让您感觉更好,则在其上扔一个@CompileStatic。您是否认为Scala是一种脚本语言?可能是Java造成了这种la脚。您正在运行JVM,请充分利用它。 (2认同)
  • 你得到 -1 不是因为我认为 Grovy 是一种脚本语言,而是因为问题是关于 java 的。 (2认同)