知识大全 Java 实践: 用动态代理进行修饰

Posted

篇首语:与有肝胆人共事,从无字句处读书。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 Java 实践: 用动态代理进行修饰相关的知识,希望对你有一定的参考价值。

Java 实践: 用动态代理进行修饰  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  动态代理工具 是 java lang reflect 包的一部分 在 JDK 版本中添加到 JDK 它允许程序创建 代理对象 代理对象能实现一个或多个已知接口 并用反射代替内置的虚方法分派 编程地分派对接口方法的调用 这个过程允许实现 截取 方法调用 重新路由它们或者动态地添加功能

  动态代理为实现许多常见设计模式(包括 Facade Bridge Interceptor Decorator Proxy(包括远程和虚拟代理)和 Adapter 模式)提供了替代的动态机制 虽然这些模式不使用动态代理 只用普通的类就能够实现 但是在许多情况下 动态代理方式更方便 更紧凑 可以清除许多手写或生成的类

  Proxy模式

  Proxy 模式中要创建 stub 或 surrogate 对象 它们的目的是接受请求并把请求转发到实际执行工作的其他对象 远程方法调用(RMI)利用 Proxy 模式 使得在其他 JVM 中执行的对象就像本地对象一样 企业 JavaBeans (EJB)利用 Proxy 模式添加远程调用 安全性和事务分界 而 JAX RPC Web 服务则用 Proxy 模式让远程服务表现得像本地对象一样 在每一种情况中 潜在的远程对象的行为是由接口定义的 而接口本质上接受多种实现 调用者(在大多数情况下)不能区分出它们只是持有一个对 stub 而不是实际对象的引用 因为二者实现了相同的接口 stub 的工作是查找实际的对象 封送参数 把参数发送给实际对象 解除封送返回值 把返回值返回给调用者 代理可以用来提供远程控制(就像在 RMI EJB 和 JAX RPC 中那样) 用安全性策略包装对象(EJB) 为昂贵的对象(EJB 实体 Bean)提供惰性装入 或者添加检测工具(例如日志记录)

  在 以前的 JDK 中 RMI stub(以及它对等的 skeleton)是在编译时由 RMI 编译器(rmic)生成的类 RMI 编译器是 JDK 工具集的一部分 对于每个远程接口 都会生成一个 stub(代理)类 它代表远程对象 还生成一个 skeleton 对象 它在远程 JVM 中做与 stub 相反的工作 —— 解除封送参数并调用实际的对象 类似地 用于 Web 服务的 JAX RPC 工具也为远程 Web 服务生成代理类 从而使远程 Web 服务看起来就像本地对象一样

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

  动态代理机制

  动态代理机制的核心是 InvocationHandler 接口 如清单 所示 调用句柄的工作是代表动态代理实际执行所请求的方法调用 传递给调用句柄一个 Method 对象(从 java lang reflect 包) 参数列表则传递给方法 在最简单的情况下 可能仅仅是调用反射性的方法 Method invoke() 并返回结果

  清单 InvocationHandler 接口

  public interface InvocationHandler Object invoke(Object proxy Method method Object[] args) throws Throwable;

  每个代理都有一个与之关联的调用句柄 只要代理的方法被调用时就会调用该句柄 根据通用的设计原则 接口定义类型 类定义实现 代理对象可以实现一个或多个接口 但是不能实现类 因为代理类没有可以访问的名称 它们不能有构造函数 所以它们必须由工厂创建 清单 显示了动态代理的最简单的可能实现 它实现 Set 接口并把所有 Set 方法(以及所有 Object 方法)分派给封装的 Set 实例

  清单 包装 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 这样什么都不做的包装器来说 实际有个很好的应用 —— 可以用它安全地把对象引用的范围缩小到特定接口(或接口集)上 方式是 调用者不能提升引用的类型 使得可以更安全地把对象引用传递给不受信任的代码(例如插件或回调) 清单 包含一组类定义 实现了典型的回调场景 从中会看到动态代理可以更方便地替代通常用手工(或用 IDE 提供的代码生成向导)实现的 Adapter 模式

  清单 典型的回调场景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 调用的方法 有时对这个风险并不关心 —— 但有时却关心 如果关心 那么可以把回调对象作为内部类 或者编写一个什么都不做的适配器类(请参阅清单 中的 ServiceCallbackAdapter)并用 ServiceCallbackAdapter 包装 ServiceConsumer ServiceCallbackAdapter 防止 Service 把 ServiceCallback 提升为 ServiceConsumer

  清单 用于安全地把对象限制在一个接口上以便不被恶意代码不能的适配器类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 向导 降低了编写适配器类的工作量 但是仍然必须为每个想要包装的接口编写一个适配器类 而且对于只包含生成的代码的类 也有一些让人不满意的地方 看起来应当有一种方式可以更紧凑地表示 什么也不做的限制适配器模式

  通用适配器类

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

  清单 通用的限制适配器工厂类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

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

  清单 基于代理的 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= ; args != null && i<args length; i++) if (i != ) 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 (!ntains( 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 中

  清单 显示的动态代理检查被调用方法的名称 并通过查询或修改属性图直接实现 getter 和 setter 方法 现在 这一个代理类就能实现多个 JavaBean 风格接口的对象

  清单 用于把 getter 和 setter 分派给 Map 的动态代理类

  public class JavaBeanProxyFactory private static class JavaBeanProxy implements InvocationHandler Map<String Object> properties = new HashMap<String Object>(); public JavaBeanProxy(Map<String Object> properties) this properties 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( ); Object o = properties 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 similarly else if (meth startsWith( is )) // Alternate version of get for boolean properties else // Can dispatch non get/set/is methods as desired public static<T> T getProxy(Class<T> intf Map<String Object> values) return (T) Proxy newProxyInstance (JavaBeanProxyFactory class getClassLoader() new Class[] intf new JavaBeanProxy(values));

  虽然因为反射在 Object 上工作会有潜在的类型安全性上的损失 但是 JavaBeanProxyFactory 中的 getter 处理会进行一些必要的额外的类型检测 就像我在这里用 isInstance() 对 getter 进行的检测一样

  性能成本

  正如已经看到的 动态代理拥有简化大量代码的潜力 —— 不仅能替代许多生成的代码 而且一个代理类还能代替多个手写的类或生成的代码 什么是成本呢?因为反射地分派方法而不是采用内置的虚方法分派 可能有一些性能上的成本 在早期的 JDK 中 反射的性能很差(就像早期 JDK 中几乎其他每件事的性能一样) 但是在近 年 反射已经变得快多了

  不必进入基准测试构造的主题 我编写了一个简单的 不太科学的测试程序 它循环地把数据填充到 Set 随机地对 Set进行插入 查询和删除元素 我用三个 Set 实现运行它 一个未经修饰的 HashSet 一个手写的 只是把所有方法转发到底层的 HashSet 的 Set 适配器 还有一个基于代理的 也只是把所有方法转发到底层 HashSet 的 Set 适配器 每次循环迭代都生成若干随机数 并执行一个或多个 Set 操作 手写的适配器比起原始的 HashSet 只产生很少百分比的性能负荷(大概是因为 JVM 级有效的内联缓冲和硬件级的分支预测) 代理适配器则明显比原始 HashSet 慢 但是开销要少于两个量级

  我从这个试验得出的结论是 对于大多数情况 代理方式即使对轻量级方法也执行得足够好 而随着被代理的操作变得越来越重量级(例如远程方法调用 或者使用序列化 执行 IO 或者从数据库检索数据的方法) 代理开销就会有效地接近于 当然也存在一些代理方式的性能开销无法接受的情况 但是这些通常只是少数情况

  结束语

cha138/Article/program/Java/hx/201311/26667

相关参考

知识大全 JAVA 动态代理

JAVA动态代理  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  在目前的Java开发包中包含了对

知识大全 java动态代理Dynamic Proxy

java动态代理DynamicProxy  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  被代理对

知识大全 Java动态代理机制综合分析及扩展

Java动态代理机制综合分析及扩展  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java动态代

知识大全 Java设计模式-----Proxy模式(动态代理)

Java设计模式-----Proxy模式(动态代理)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

知识大全 浅谈Java中用动态代理类实现记忆功能

浅谈Java中用动态代理类实现记忆功能  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  记忆是衍生

知识大全 spring技术手册上的一个java动态代理例子

  Java代码  publicinterfaceIHello  publicvoidhello(Stringname);    Java代码  publicclassHelloImplimpleme

知识大全 JVM技术,反射与动态代理

JVM技术,反射与动态代理  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java程序的工作机制

知识大全 Java 实践: 用 XQuery 进行屏幕搜集

Java实践:用XQuery进行屏幕搜集  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  上个月J

知识大全 动态代理

  动态代理  要求测算一个类里面的某个方法的执行时间  直接在一个测试的方法中调用这个类的方法就可以了也可以直接的写在这个类的main方法里面  继承这个类对需要测算时间的方法进行重写方法中这样来写

知识大全 Java设计模式之修饰模式篇

Java设计模式之修饰模式篇  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  最近我给女朋友买了一