Java理论与实践 用动态代理进行修饰_第1页
Java理论与实践 用动态代理进行修饰_第2页
Java理论与实践 用动态代理进行修饰_第3页
Java理论与实践 用动态代理进行修饰_第4页
Java理论与实践 用动态代理进行修饰_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Java 理论与实践: 用动态代理进行修饰动态代理为实现许多常见设计模式(包括 Facade、Bridge、Interceptor、Decorator、Proxy(包括远程和虚拟代理)和 Adapter 模式)提供了替代的动态机制。虽然这些模式不使用动态代理,只用普通的类就能够实现,但是在许多情况下,动态代理方式更方便、更紧凑,可以清除许多手写或生成的类。Proxy 模式Proxy 模式中要创建“stub”或“surrogate”对象,它们的目的是接受请求并把请求转发到实际执行工作的其他对象。远程方法调用(RMI)利用 Proxy模式,使得在其他 JVM 中执行的对象就像本地对象一样;企业 J

2、avaBeans(EJB)利用 Proxy 模式添加远程调用、安全性和事务分界;而 JAX-RPC Web服务则用 Proxy 模式让远程服务表现得像本地对象一样。在每一种情况中,潜在的远程对象的行为是由接口定义的,而接口本质上接受多种实现。调用者(在大多数情况下)不能区分出它们只是持有一个对 stub 而不是实际对象的引用,因为二者实现了相同的接口;stub 的工作是查找实际的对象、封送参数、把参数发送给实际对象、解除封送返回值、把返回值返回给调用者。代理可以用来提供远程控制(就像在 RMI、EJB 和 JAX-RPC 中那样),用安全性策略包装对象(EJB)、为昂贵的对象(EJB 实体 B

3、ean)提供惰性装入,或者添加检测工具(例如日志记录)。在 5.0 以前的 JDK 中,RMI stub(以及它对等的 skeleton)是在编译时由 RMI 编译器(rmic)生成的类,RMI 编译器是 JDK 工具集的一部分。对于每个远程接口,都会生成一个 stub(代理)类,它代表远程对象,还生成一个skeleton 对象,它在远程 JVM 中做与 stub 相反的工作 解除封送参数并调用实际的对象。类似地,用于 Web 服务的 JAX-RPC 工具也为远程 Web 服务生成代理类,从而使远程 Web 服务看起来就像本地对象一样。不管 stub 类是以源代码还是以字节码生成的,代码生成仍

4、然会向编译过程添加一些额外步骤,而且因为命名相似的类的泛滥,会带来意义模糊的可能性。另一方面,动态代理机制支持在编译时没有生成 stub 类的情况下,在运行时创建代理对象。在 JDK 5.0 及以后版本中,RMI 工具使用动态代理代替了生成的 stub,结果 RMI 变得更容易使用。许多 J2EE 容器也使用动态代理来实现 EJB。EJB 技术严重地依靠使用拦截(interception)来实现安全性和事务分界;动态代理为接口上调用的所有方法提供了集中的控制流程路径。动态代理机制动态代理机制的核心是 InvocationHandler 接口,如清单 1 所示。调用句柄的工作是代表动态代理实际执

5、行所请求的方法调用。传递给调用句柄一个Method 对象(从 java.lang.reflect 包),参数列表则传递给方法;在最简单的情况下,可能仅仅是调用反射性的方法 Method.invoke() 并返回结果。清单 1. InvocationHandler 接口public interface InvocationHandler Object invoke(Object proxy, Method method, Object args)throws Throwable;每个代理都有一个与之关联的调用句柄,只要代理的方法被调用时就会调用该句柄。根据通用的设计原则:接口定义类型、类定义实现

6、,代理对象可以实现一个或多个接口,但是不能实现类。因为代理类没有可以访问的名称,它们不能有构造函数,所以它们必须由工厂创建。清单 2 显示了动态代理的最简单的可能实现,它实现 Set 接口并把所有 Set 方法(以及所有 Object 方法)分派给封装的 Set 实例。清单 2. 包装 Set 的简单的动态代理public class SetProxyFactory public static Set getSetProxy(final Set s) return (Set) Proxy.newProxyInstance(s.getClass().getClassLoader(),new Cl

7、ass 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 实例。虽

8、然反射代码通常比较难读,但是这里的内容很少,跟上控制流程并不难 只要某个方法在 Set 代理上被调用,它就被分派给调用句柄,调用句柄只是反射地调用底层包装的对象上的目标方法。当然,绝对什么都不做的代理可能有点傻,是不是呢?什么都不做的适配器对于像 SetProxyFactory 这样什么都不做的包装器来说,实际有个很好的应用 可以用它安全地把对象引用的范围缩小到特定接口(或接口集)上,方式是,调用者不能提升引用的类型,使得可以更安全地把对象引用传递给不受信任的代码(例如插件或回调)。清单 3 包含一组类定义,实现了典型的回调场景。从中会看到动态代理可以更方便地替代通常用手工(或用 IDE 提供

9、的代码生成向导)实现的 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(

10、this);ServiceConsumer 类实现了 ServiceCallback(这通常是支持回调的一个方便途径)并把 this 引用传递给 serviceMethod() 作为回调引用。这种方法的问题是没有机制可以阻止 Service 实现把 ServiceCallback 提升为ServiceConsumer,并调用 ServiceConsumer 不希望 Service 调用的方法。有时对这个风险并不关心 但有时却关心。如果关心,那么可以把回调对象作为内部类,或者编写一个什么都不做的适配器类(请参阅清单 4 中的ServiceCallbackAdapter)并用 ServiceCal

11、lbackAdapter 包装ServiceConsumer。ServiceCallbackAdapter 防止 Service 把ServiceCallback 提升为 ServiceConsumer。清单 4. 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类public class ServiceCallbackAdapter implements ServiceCallback private final ServiceCallback cb;public ServiceCallbackAdapter(ServiceCallback cb) this.cb = cb;pu

12、blic void doCallback() cb.doCallback();编写 ServiceCallbackAdapter 这样的适配器类简单却乏味。必须为包装的接口中的每个方法编写重定向类。在 ServiceCallback 的示例中,只有一个需要实现的方法,但是某些接口,例如 Collections 或 JDBC 接口,则包含许多方法。现代的 IDE 提供了“Delegate Methods”向导,降低了编写适配器类的工作量,但是仍然必须为每个想要包装的接口编写一个适配器类,而且对于只包含生成的代码的类,也有一些让人不满意的地方。看起来应当有一种方式可以更紧凑地表示“什么也不做的限制

13、适配器模式”。通用适配器类清单 2 中的 SetProxyFactory 类当然比用于 Set 的等价的适配器类更紧凑,但是它仍然只适用于一个接口:Set。但是通过使用泛型,可以容易地创建通用的代理工厂,由它为任何接口做同样的工作,如清单 5 所示。它几乎与SetProxyFactory 相同,但是可以适用于任何接口。现在再也不用编写限制适配器类了!如果想创建代理对象安全地把对象限制在接口 T,只要调用getProxy(T.class,object) 就可以了,不需要一堆适配器类的额外累赘。清单 5. 通用的限制适配器工厂类public class GenericProxyFactory pu

14、blic 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););动态代理作为 Decorator当然,动态代理工具能做的,远不仅仅是把

15、对象类型限制在特定接口上。从 清单 2 和 清单 5 中简单的限制适配器到 Decorator 模式,是一个小的飞跃,在 Decorator 模式中,代理用额外的功能(例如安全检测或日志记录)包装调用。清单 6 显示了一个日志 InvocationHandler,它在调用目标对象上的方法之外,还写入一条日志信息,显示被调用的方法、传递的参数,以及返回值。除了反射性的 invoke() 调用之外,这里的全部代码只是生成调试信息的一部分 还不是太多。代理工厂方法的代码几乎与 GenericProxyFactory相同,区别在于它使用的是 LoggingInvocationHandler 而不是匿名

16、的调用句柄。清单 6. 基于代理的 Decorator,为每个方法调用生成调试日志private static class LoggingInvocationHandlerimplements 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

17、StringBuffer();sb.append(method.getName(); sb.append();for (int i=0; args != null & i ); 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);会得到以下输出:

18、add(three) - truecontains(four) - falseadd(four) - truetoString() - four, threefour, three这种方式是给对象添加调试包装器的一种好的而且容易的方式。它当然比生成代理类并手工创建大量 println() 语句容易得多(也更通用)。我进一步改进了这一方法;不必无条件地生成调试输出,相反,代理可以查询动态配置存储(从配置文件初始化,可以由 JMX MBean 动态修改),确定是否需要生成调试语句,甚至可能在逐个类或逐个实例的基础上进行。在这一点上,我认为读者中的 AOP 爱好者们几乎要跳出来说“这正是 AOP擅长

19、的啊!”是的,但是解决问题的方法不止一种 仅仅因为某项技术能解决某个问题,并不意味着它就是最好的解决方案。在任何情况下,动态代理方式都有完全在“纯 Java”范围内工作的优势,不是每个公司都用(或应当用) AOP 的。动态代理作为适配器代理也可以用作真正的适配器,提供了对象的一个视图,导出与底层对象实现的接口不同的接口。调用句柄不需要把每个方法调用都分派给相同的底层对象;它可以检查名称,并把不同的方法分派给不同的对象。例如,假设有一组表示持久实体(Person、Company 和 PurchaseOrder) 的 JavaBean 接口,指定了属性的 getter 和 setter,而且正在编

20、写一个持久层,把数据库记录映射到实现这些接口的对象上。现在不用为每个接口编写或生成类,可以只用一个 JavaBean 风格的通用代理类,把属性保存在 Map 中。清单 7 显示的动态代理检查被调用方法的名称,并通过查询或修改属性图直接实现 getter 和 setter 方法。现在,这一个代理类就能实现多个JavaBean 风格接口的对象。清单 7. 用于把 getter 和 setter 分派给 Map 的动态代理类public class JavaBeanProxyFactory private static class JavaBeanProxy implements Invocatio

21、nHandler Map properties = new HashMap();public JavaBeanProxy(Map properties) perties.putAll(properties);public Object invoke(Object proxy, Method method,Object args)throws Throwable String meth = method.getName();if (meth.startsWith(get) String prop = meth.substring(3);Object o = properties.

22、get(prop);if (o != null & !method.getReturnType().isInstance(o)throw new ClassCastException(o.getClass().getName() + is not a + method.getReturnType().getName();return o;else if (meth.startsWith(set) / Dispatch setters similarlyelse if (meth.startsWith(is) / Alternate version of get for boolean

23、propertieselse / Can dispatch non get/set/is methods as desiredpublic static T getProxy(Class intf,Map values) return (T) Proxy.newProxyInstance(JavaBeanProxyFactory.class.getClassLoader(),new Class intf , new JavaBeanProxy(values);虽然因为反射在 Object 上工作会有潜在的类型安全性上的损失,但是,JavaBeanProxyFactory 中的 getter 处理会进行一些必要的额外的类型检测,就像我在这里用 isInstance() 对 getter 进行的检测一样。性能成本正如已经看到的,动态代理拥有简化大量代码的潜力 不仅能替代许多生成的代码,而且一个代理类还

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论