自动委派 java 类的所有方法



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

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

顺便说一句:我不能使用继承,因为委托不受我的控制。我只是从其他地方获得它的实例(另一种情况是如果 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在他的回答的评论中所讨论的那样,不必在WrapperClass中创建和维护一长串MyClass方法(即 public void methodN() { delegate.methodN(); } (,动态代理解决方案将其移动到接口。问题是您仍然必须在界面中为MyClass方法创建和维护一长串签名,这可能更简单一些,但不能完全解决问题。如果您无权访问MyClass以了解所有方法,则尤其如此。

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

对于较长的类,程序员必须选择两害相权取其轻: 实现许多包装器方法并保留修饰对象的类型 或维护简单的装饰器实现并牺牲保留 修饰对象类型。

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

然而,@Mark-Bramnik给出了一个使用CGLIB在Java类方法(没有接口(上的插入的迷人解决方案。我能够将其与@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); }
}

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

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

这是扩展MyClassMyInterceptor。它采用@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为这个问题提供了一个非常酷的解决方案。

检查龙

目岛框架中的@Delegate注释:https://projectlombok.org/features/experimental/Delegate

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

你不必这样做——你的 Wrapper 类是原始类的子类,所以它继承了它所有可公开访问的方法——如果你不实现它们,将调用原始方法。

你不应该extends Myclass一个私有MyClass对象 - 这真的是多余的,我想不出这样做是正确的设计模式。您的WrapperClass是一个MyClass,因此您可以使用自己的字段和方法,而不是调用delegate

编辑:在MyClassfinal的情况下,您将绕过故意声明,通过"伪造"继承来不允许子类化;除了控制WrapperClass的你之外,我想不出还有谁愿意这样做;但是,既然你控制了WrapperClass,不包装你不需要的所有东西实际上不仅仅是一个选择——这是正确的做法,因为你的对象不是MyClass,只有在你心理考虑的情况下才应该表现得像一个。

编辑 您刚刚通过将MyClass超类删除到您的WrapperClass中来将您的问题更改为完全不同的含义; 这有点糟糕,因为它使到目前为止给出的所有答案无效。你应该打开另一个问题。

Credits 转到 CoronA 以指出代理和 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中定义一个方法,即 返回MyClass实例的delegate()

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

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

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

实际上,您当前的WrapperClass实现实际上是MyClass之上的装饰器

让我重新定义特定案例的问题。我想覆盖 jdbc 中结果集接口的关闭方法。我的目标是在结果集的关闭方法中关闭准备语句。 我无法访问在结果集接口中实现的类(委派结果集(。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);

我真的很感激@CoronA的回答。我也看了@Mark Cramer的回答,但是,如果我没有遗漏什么,我认为总是至少有两个"代理"类的实例,在这两个对象之间有着奇怪的关系。

这一点,再加上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 实例时,委托才会实际工作。

相关内容

  • 没有找到相关文章

最新更新