假设我有一个包含许多公共方法的类:
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);
}
}
这是扩展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为这个问题提供了一个非常酷的解决方案。
目岛框架中的@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
。
编辑:在MyClass
被final
的情况下,您将绕过故意声明,通过"伪造"继承来不允许子类化;除了控制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
实例时,委托才会实际工作。