Java理论与实践:用动态代理进行修饰-2

上一篇 / 下一篇  2012-08-14 11:30:08 / 个人分类:Java

51Testing软件测试网 imPV"Y7w

  如果用日志代理包装 HashSet,并执行下面这个简单的测试程序:51Testing软件测试网|k+bcfk.t

51Testing软件测试网 @Y:]#I'e/Mm

51Testing软件测试网 fLag^ y

  1. Set s = newLoggingProxy(Set.classnew HashSet());  
  2.  s.add("three");  
  3.  if (!s.contains("four"))  
  4.  s.add("four");  
  5.  System.out.println(s);

.htQ-Ts(U6h;Kk0  会得到以下输出:51Testing软件测试网6aU/x&O~/E [t1q(L

9K-] ]&PGu051Testing软件测试网 F A;xN#w

  1. add(three) -> true  
  2.  
  3. contains(four) -> false  
  4.  
  5. add(four) -> true  
  6.  
  7. toString() -> [four, three]  
  8.  
  9. [four, three]
51Testing软件测试网9u#_8Wl3Eh/VV7`

  这种方式是给对象添加调试包装器的一种好的而且容易的方式。它当然比生成代理类并手工创建大量 println() 语句容易得多(也更通用)。我进一步改进了这一方法;不必无条件地生成调试输出,相反,代理可以查询动态配置存储(从配置文件初始化,可以由 JMX MBean 动态修改),确定是否需要生成调试语句,甚至可能在逐个类或逐个实例的基础上进行。51Testing软件测试网V i$_}9F,w.M

51Testing软件测试网'nG? H T%L8P2C}

  在这一点上,我认为读者中的 AOP 爱好者们几乎要跳出来说“这正是 AOP 擅长的啊!”是的,但是解决问题的方法不止一种 —— 仅仅因为某项技术能解决某个问题,并不意味着它就是最好的解决方案。在任何情况下,动态代理方式都有完全在“纯 Java”范围内工作的优势,不是每个公司都用(或应当用) AOP 的。

?1I1\ H cg6d? X051Testing软件测试网2V[ss%i!bvA

  动态代理作为适配器

9z7A#]q/r8]A0

+\cL5n+VIW0  代理也可以用作真正的适配器,提供了对象的一个视图,导出与底层对象实现的接口不同的接口。调用句柄不需要把每个方法调用都分派给相同的底层对 象;它可以检查名称,并把不同的方法分派给不同的对象。例如,假设有一组表示持久实体 (Person、Company 和 PurchaseOrder) 的 JavaBean 接口,指定了属性的 getter 和 setter,而且正在编写一个持久层,把数据库记录映射到实现这些接口的对象上。现在不用为每个接口编写或生成类,可以只用一个 JavaBean 风格的通用代理类,把属性保存在 Map 中。51Testing软件测试网R/|'v-Q9[k)|a+L4cz

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

:x-Jz\Z a]X051Testing软件测试网@ p%G*I J8k_2}B

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

A?3f!X3Noz {p051Testing软件测试网 ti%]#RyIP

_,y(F*btK6D#a0
  1. public class JavaBeanProxyFactory {  
  2.  private static class JavaBeanProxy implements InvocationHandler {  
  3.  Map properties = new HashMap();  
  4. public JavaBeanProxy(Mapproperties) {  
  5.  this.properties.putAll(properties);  
  6.  }  
  7. public Object invoke(Object proxy, Method method,  
  8.  Object[] args)  
  9.  throws Throwable {  
  10.  String meth = method.getName();  
  11.  if (meth.startsWith("get")) {  
  12.  String prop = meth.substring(3);  
  13.  Object o = properties.get(prop);  
  14.  if (o != null && !method.getReturnType().isInstance(o))  
  15.  throw new ClassCastException(o.getClass().getName() +  
  16.  " is not a " + method.getReturnType().getName());  
  17.  return o;  
  18.  }  
  19.  else if (meth.startsWith("set")) {  
  20.  // Dispatch setters similarly 
  21.  }  
  22.  else if (meth.startsWith("is")) {  
  23.  // Alternate version of get for boolean properties 
  24.  }  
  25.  else {  
  26.  // Can dispatch non get/set/is methods as desired 
  27.  }  
  28.  }  
  29.  }  
  30. public staticT getProxy(Classintf,  
  31.  Map values) {  
  32.  return (T) Proxy.newProxyInstance  
  33.  (JavaBeanProxyFactory.class.getClassLoader(),  
  34.  new Class[] { intf }, new JavaBeanProxy(values));  
  35.  }  
  36.  }
  37. 51Testing软件测试网-q2zQAAE%I4D6E

      虽然因为反射在 Object 上工作会有潜在的类型安全性上的损失,但是,JavaBeanProxyFactory 中的 getter 处理会进行一些必要的额外的类型检测,就像我在这里用 isInstance() 对 getter 进行的检测一样。51Testing软件测试网0PL1O-u?

    o.{T6}*~2]0  性能成本

    x/N'ORPwS6e051Testing软件测试网$jh.E's+H/Q0d!G

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

    51Testing软件测试网:Vj F"{P)`W

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

    ,dI%R_*D*|!hL | [.K Yq051Testing软件测试网nWg;d:]-uz\oP

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

    W!_+e+z~*j JE051Testing软件测试网`c m+Y ^2p

      结束语

    l!N?tb,Ky051Testing软件测试网dQuEYGb9i4e

      动态代理是强大而未充分利用的工具,可以用于实现许多设计模式,包括 Proxy、Decorator 和 Adapter。这些模式基于代理的实现容易编写,更难出错,并且具备更好的通用性;在许多情况下,一个动态代理类可以充当所有接口的 Decorator 或 Proxy,这样就不用每个接口都编写一个静态类。除了最关注性能的应用程序之外,动态代理方式可能比手写或机器生成 stub 的方式更可取。51Testing软件测试网C~(UMRy ^#@


TAG:

 

评分:0

我来说两句

Open Toolbar