什么都不做的适配器 对于像 SetProxyFactory 这样什么都不做的包装器来说,实际有个很好的应用 —— 可以用它安全地把对象引用的范围缩小到特定接口(或接口集)上,方式是,调用者不能提升引用的类型,使得可以更安全地把对象引用传递给不受信任的代码(例如插件或回调)。清单 3 包含一组类定义,实现了典型的回调场景。从中会看到动态代理可以更方便地替代通常用手工(或用 IDE 提供的代码生成向导)实现的 Adapter 模式。: X5 I! T; R7 z" m! V B
清单 3. 典型的回调场景public interface ServiceCallback { public void doCallback(); } public interface Service { public void serviceMethod(ServiceCallback callback); } public class ServiceConsumer implements ServiceCallback { private Service service; ... public void someMethod() { ... service.serviceMethod(this); } }
1 r8 p+ _- b2 W# V- a ServiceConsumer 类实现了 ServiceCallback(这通常是支持回调的一个方便途径)并把 this 引用传递给 serviceMethod() 作为回调引用。这种方法的问题是没有机制可以阻止 Service 实现把 ServiceCallback 提升为 ServiceConsumer,并调用 ServiceConsumer 不希望 Service 调用的方法。有时对这个风险并不关心 —— 但有时却关心。如果关心,那么可以把回调对象作为内部类,或者编写一个什么都不做的适配器类(请参阅清单 4 中的 ServiceCallbackAdapter)并用 ServiceCallbackAdapter 包装 ServiceConsumer。ServiceCallbackAdapter 防止 Service 把 ServiceCallback 提升为 ServiceConsumer。
+ T! m7 ^* V3 ]7 R. L 清单 4. 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类public class ServiceCallbackAdapter implements ServiceCallback { private final ServiceCallback cb; public ServiceCallbackAdapter(ServiceCallback cb) { this.cb = cb; } public void doCallback() { cb.doCallback(); } }% x$ y' I; o0 ?! i- {; z* L
编写 ServiceCallbackAdapter 这样的适配器类简单却乏味。必须为包装的接口中的每个方法编写重定向类。在 ServiceCallback 的示例中,只有一个需要实现的方法,但是某些接口,例如 Collections 或 JDBC 接口,则包含许多方法。现代的 IDE 提供了“Delegate Methods”向导,降低了编写适配器类的工作量,但是仍然必须为每个想要包装的接口编写一个适配器类,而且对于只包含生成的代码的类,也有一些让人不满意的地方。看起来应当有一种方式可以更紧凑地表示“什么也不做的限制适配器模式”。
( c5 C* J \- g) y% ?5 I% `9 W 通用适配器类
( R% v; \) R+ d) a8 A$ t 清单 2 中的 SetProxyFactory 类当然比用于 Set 的等价的适配器类更紧凑,但是它仍然只适用于一个接口:Set。但是通过使用泛型,可以容易地创建通用的代理工厂,由它为任何接口做同样的工作,如清单 5 所示。它几乎与 SetProxyFactory 相同,但是可以适用于任何接口。现在再也不用编写限制适配器类了!如果想创建代理对象安全地把对象限制在接口 T,只要调用 getProxy(T.class,object) 就可以了,不需要一堆适配器类的额外累赘。
- C+ @, L0 A2 \ c1 x 清单 5. 通用的限制适配器工厂类public class GenericProxyFactory { public static T getProxy(Class intf, final T obj) { return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), new Class[] { intf }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(obj, args); } }); } }2 p* {6 x3 s) n8 [ v* o0 N, m- P
动态代理作为Decorator
' b' b5 h1 k2 B- T0 b. \3 q! z 当然,动态代理工具能做的,远不仅仅是把对象类型限制在特定接口上。从 清单 2 和 清单 5 中简单的限制适配器到 Decorator 模式,是一个小的飞跃,在 Decorator 模式中,代理用额外的功能(例如安全检测或日志记录)包装调用。清单 6 显示了一个日志 InvocationHandler,它在调用目标对象上的方法之外,还写入一条日志信息,显示被调用的方法、传递的参数,以及返回值。除了反射性的 invoke() 调用之外,这里的全部代码只是生成调试信息的一部分 —— 还不是太多。代理工厂方法的代码几乎与 GenericProxyFactory 相同,区别在于它使用的是 LoggingInvocationHandler 而不是匿名的调用句柄。
& p! ~4 J) e! ~6 P5 k 清单 6. 基于代理的 Decorator,为每个方法调用生成调试日志
, n0 p) D; z! z1 } private static class LoggingInvocationHandler implements InvocationHandler { final T underlying; public LoggingHandler(T underlying) { this.underlying = underlying; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { StringBuffer sb = new StringBuffer(); sb.append(method.getName()); sb.append("("); for (int i=0; args != null && i "); sb.append(ret); } System.out.println(sb); return ret; } }
+ \7 K/ y. I' k% z# t7 v3 w 如果用日志代理包装 HashSet,并执行下面这个简单的测试程序:3 @: F, y1 W: t
) T2 H8 W) H4 N' \5 c! ?" V8 q
Set s = newLoggingProxy(Set.class, new HashSet()); s.add("three"); if (!s.contains("four")) s.add("four"); System.out.println(s); |