会计考友 发表于 2012-8-4 12:44:44

Java认证辅导之关于用动态代理进行修饰(2)

Java认证辅导之关于用动态代理进行修饰(2)


清单 2. 包装 Set 的简单的动态代理
public class SetProxyFactory { public static Set getSetProxy(final Set s) { return (Set) Proxy.newProxyInstance (s.getClass().getClassLoader(), new Class[] { Set.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(s, args); } }); } }
SetProxyFactory 类包含一个静态工厂方法 getSetProxy(),它返回一个实现了 Set 的动态代理。代理对象实际实现 Set —— 调用者无法区分(除非通过反射)返回的对象是动态代理。SetProxyFactory 返回的代理只做一件事,把方法分派给传递给工厂方法的 Set 实例。虽然反射代码通常比较难读,但是这里的内容很少,跟上控制流程并不难 —— 只要某个方法在 Set 代理上被调用,它就被分派给调用句柄,调用句柄只是反射地调用底层包装的对象上的目标方法。当然,绝对什么都不做的代理可能有点傻,是不是呢?
什么都不做的适配器
对于像 SetProxyFactory 这样什么都不做的包装器来说,实际有个很好的应用 —— 可以用它安全地把对象引用的范围缩小到特定接口(或接口集)上,方式是,调用者不能提升引用的类型,使得可以更安全地把对象引用传递给不受信任的代码(例如插件或回调)。清单 3 包含一组类定义,实现了典型的回调场景。从中会看到动态代理可以更方便地替代通常用手工(或用 IDE 提供的代码生成向导)实现的 Adapter 模式。
清单 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); } }
ServiceConsumer 类实现了 ServiceCallback(这通常是支持回调的一个方便途径)并把 this 引用传递给 serviceMethod() 作为回调引用。这种方法的问题是没有机制可以阻止 Service 实现把 ServiceCallback 提升为 ServiceConsumer,并调用 ServiceConsumer 不希望 Service 调用的方法。有时对这个风险并不关心 —— 但有时却关心。如果关心,那么可以把回调对象作为内部类,或者编写一个什么都不做的适配器类(请参阅清单 4 中的 ServiceCallbackAdapter)并用 ServiceCallbackAdapter 包装 ServiceConsumer。ServiceCallbackAdapter 防止 Service 把 ServiceCallback 提升为 ServiceConsumer。
清单 4. 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类public class ServiceCallbackAdapter implements ServiceCallback { private final ServiceCallback cb; public ServiceCallbackAdapter(ServiceCallback cb) { this.cb = cb; } public void doCallback() { cb.doCallback(); } }
编写 ServiceCallbackAdapter 这样的适配器类简单却乏味。必须为包装的接口中的每个方法编写重定向类。在 ServiceCallback 的示例中,只有一个需要实现的方法,但是某些接口,例如 Collections 或 JDBC 接口,则包含许多方法。现代的 IDE 提供了“Delegate Methods”向导,降低了编写适配器类的工作量,但是仍然必须为每个想要包装的接口编写一个适配器类,而且对于只包含生成的代码的类,也有一些让人不满意的地方。看起来应当有一种方式可以更紧凑地表示“什么也不做的限制适配器模式”。
通用适配器类
清单 2 中的 SetProxyFactory 类当然比用于 Set 的等价的适配器类更紧凑,但是它仍然只适用于一个接口:Set。但是通过使用泛型,可以容易地创建通用的代理工厂,由它为任何接口做同样的工作,如清单 5 所示。它几乎与 SetProxyFactory 相同,但是可以适用于任何接口。现在再也不用编写限制适配器类了!如果想创建代理对象安全地把对象限制在接口 T,只要调用 getProxy(T.class,object) 就可以了,不需要一堆适配器类的额外累赘。
清单 5. 通用的限制适配器工厂类public class GenericProxyFactory { public static《T》 T getProxy(Class《T》 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); } }); } }
动态代理作为Decorator
当然,动态代理工具能做的,远不仅仅是把对象类型限制在特定接口上。从 清单 2 和 清单 5 中简单的限制适配器到 Decorator 模式,是一个小的飞跃,在 Decorator 模式中,代理用额外的功能(例如安全检测或日志记录)包装调用。清单 6 显示了一个日志 InvocationHandler,它在调用目标对象上的方法之外,还写入一条日志信息,显示被调用的方法、传递的参数,以及返回值。除了反射性的 invoke() 调用之外,这里的全部代码只是生成调试信息的一部分 —— 还不是太多。代理工厂方法的代码几乎与 GenericProxyFactory 相同,区别在于它使用的是 LoggingInvocationHandler 而不是匿名的调用句柄。
清单 6. 基于代理的 Decorator,为每个方法调用生成调试日志
private static class LoggingInvocationHandler《T》 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《args.length; i++) { if (i != 0) sb.append(“, ”); sb.append(args[i]); } sb.append(“)”); Object ret = method.invoke(underlying, args); if (ret != null) { sb.append(“ -》 ”); sb.append(ret); } System.out.println(sb); return ret; } }
如果用日志代理包装 HashSet,并执行下面这个简单的测试程序:
Set s = newLoggingProxy(Set.class, new HashSet()); s.add(“three”); if (!s.contains(“four”)) s.add(“four”); System.out.println(s);
会得到以下输出:
add(three) -》 true contains(four) -》 false add(four) -》 true toString() -》 [four, three] [four, three]
这种方式是给对象添加调试包装器的一种好的而且容易的方式。它当然比生成代理类并手工创建大量 println() 语句容易得多(也更通用)。我进一步改进了这一方法;不必无条件地生成调试输出,相反,代理可以查询动态配置存储(从配置文件初始化,可以由 JMX MBean 动态修改),确定是否需要生成调试语句,甚至可能在逐个类或逐个实例的基础上进行。
在这一点上,我认为读者中的 AOP 爱好者们几乎要跳出来说“这正是 AOP 擅长的啊!”是的,但是解决问题的方法不止一种 —— 仅仅因为某项技术能解决某个问题,并不意味着它就是最好的解决方案。在任何情况下,动态代理方式都有完全在“纯 Java”范围内工作的优势,不是每个公司都用(或应当用) AOP 的。
动态代理作为适配器
代理也可以用作真正的适配器,提供了对象的一个视图,导出与底层对象实现的接口不同的接口。调用句柄不需要把每个方法调用都分派给相同的底层对象;它可以检查名称,并把不同的方法分派给不同的对象。例如,假设有一组表示持久实体(Person、Company 和 PurchaseOrder) 的 JavaBean 接口,指定了属性的 getter 和 setter,而且正在编写一个持久层,把数据库记录映射到实现这些接口的对象上。现在不用为每个接口编写或生成类,可以只用一个 JavaBean 风格的通用代理类,把属性保存在 Map 中。
页: [1]
查看完整版本: Java认证辅导之关于用动态代理进行修饰(2)