自动委托java类的所有方法

时间:2015-05-20 08:50:46

标签: java reflection delegates

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

public class MyClass {

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

}

现在我想创建一个包装器类,它将所有方法委托给包装实例( 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(); }

}

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

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

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

有任何建议如何实现这样的目标? (注意:我可能会在某些脚本语言中执行此操作,例如php,我可以使用php魔术函数拦截调用)。

8 个答案:

答案 0 :(得分:20)

也许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尚未捕获特殊情况。

答案 1 :(得分:5)

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

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

根据Three approaches for decorating your code

  

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

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

然而,@ Mark-Bramnik在fascinating solution using CGLIB给出Interposing on Java Class Methods (without interfaces)。我能够将它与@ 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);
    }

}

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

}

以下是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对这个问题的解决方案。

答案 2 :(得分:4)

切换到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

答案 3 :(得分:2)

从Lombok框架检查@Delegation注释: https://projectlombok.org/features/Delegate.html

答案 4 :(得分:1)

您不必这样做 - 您的Wrapper类是原始类的子类,因此它继承了所有的公共可访问方法 - 如果您不实现它们,将调用原始方法。

您不应该将extends Myclass与私有MyClass对象放在一起 - 这实际上是多余的,我无法想到这样做的设计模式是正确的。您的WrapperClass MyClass,因此您只需使用自己的字段和方法,而不是调用delegate

编辑:在MyClassfinal的情况下,你将通过“伪造”继承来规避不允许子类化的遗嘱。除了你之外,我无法想到任何愿意做到这一点的人,谁能控制WrapperClass;但是,既然你控制了WrapperClass,那么不包装你不需要的东西不仅仅是一种选择 - 这是正确的做法,因为你的对象不是一个MyClass,在你精神上考虑的情况下应该只表现一个。

编辑您刚刚将MyClass超类移至WrapperClass,将您的问题更改为完全不同的内容;这有点糟糕,因为它使到目前为止给出的所有答案都无效。你应该打开另一个问题。

答案 5 :(得分:0)

WrapperClass中定义一个方法,即delegate(),返回MyClass的实例

OR

您可以使用反射来执行此操作,但调用者必须将方法名称作为参数传递给公开的方法。并且有关方法参数/重载方法等的并发症。

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

您发布的代码有public class WrapperClass extends MyClass

实际上,WrapperClass的当前实现实际上是MyClass

之上的装饰器

答案 6 :(得分:0)

Credits转到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中。

但是,如果我反思这一点,我可能想重新考虑为每个接口创建一个非基于反射的代理 - 它可能更清晰,更快速,更易理解。但是,这是可能的。

答案 7 :(得分:0)

让我针对特定情况重新定义问题。 我想重写jdbc中ResultSet接口的close方法。我的目的是用结果集的封闭方法封闭准备陈述。我无法访问在ResultSet接口中实现的类(DelegatingResultSet)。 ResultSet接口中有很多方法,一种一一地覆盖它们,然后从ResultSet对象中调用相应的方法是一种解决方案。对于动态解决方案,我使用了动态代理类(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);
相关问题