Java 动态代理机制分析及扩展,第 2 部分
2013-04-28 10:19
489 查看
Java 动态代理机制分析及扩展,第 2 部分
王 忠平, 软件工程师, IBM何 平, 软件工程师, IBM
简介: 相信通过阅读“Java 动态代理机制分析和扩展,第 1 部分”,读者已经对 Java 动态代理机制有了一定的了解。本文将在上一篇的基础上,针对 Java 动态代理仅支持接口代理这一局限进行扩展,实现对类的代理。
发布日期: 2010 年 1 月 27 日
级别: 中级
访问情况 : 4032 次浏览
评论: 2 (查看 | 添加评论 - 登录)
平均分 (10个评分)
为本文评分
本文希望将 Java 动态代理机制从接口扩展到类,使得类能够享有与接口类似的动态代理支持。
设计及特点
新扩展的类名为 ProxyEx,将直接继承于 java.lang.reflect.Proxy,也声明了与原 Proxy 类中同名的 public 静态方法,目的是保持与原代理机制在使用方法上的完全一致。
图 1. ProxyEx 类继承图
与原代理机制最大的区别在于,动态生成的代理类将不再从 Proxy 类继承,改而继承需被代理的类。由于 Java 的单继承原则,扩展代理机制所支持的类数目不得多于一个,但它可以声明实现若干接口。包管理的机制与原来相似,不支持一个以上的类和接口同时为非 public;如果仅有一个非 public 的类或接口,假设其包为 PackageA,则动态生成的代理类将位于包 PackageA;否则将位于被代理的类所在的包。生成的代理类也被赋予 final 和 public 访问属性,且其命名规则类似地为“父类名 +ProxyN”(N
也是递增的阿拉伯数字)。最后,在异常处理方面则与原来保持完全一致。
图 2. 动态生成的代理类的继承图
回页首
模板
通过对 Java 动态代理机制的推演,我们已经获得了一个通用的方法模板。可以预期的是,通过模板来定制和引导代理类的代码生成,是比较可行的方法。我们将主要使用两个模板:类模板和方法模板。
清单 1. 类模板
package &Package; final public class &Name &Extends &Implements { private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods } |
清单 2. 方法模板
&Modifiers &ReturnType &MethodName(&Parameters) &Throwables { java.lang.reflect.Method method = null; try { method = &Class.getMethod( \"& MethodName\", &ParameterTypes ); } catch(Exception e){ } Object r = null; try{ r = handler.invoke( this, method, &ParameterValues ); }&Exceptions &Return } |
回页首
代码生成
有了类模板和方法模板,代码生成过程就变得有章可依。基本过程可分为三步:1)生成代理类的方法集合;2)生成代理类的构造函数;3)最后生成整个代理类。
生成代理类的方法集
第一步,通过反射获得被代理类的所有 public 或 protected 且非 static 的 Method 对象列表,这些方法将被涵盖的原因是它们是可以被其他类所访问的。
第二步,遍历 Method 对象列表,对每个 Method 对象,进行相应的代码生成工作。
清单 3. 对标签位进行代码替换生成方法代码
String declTemplate = "&Modifiers &ReturnType &MethodName(&Parameters) &Throwables"; String bodyTemplate = "&Declaration &Body"; // 方法声明 String declare = declTemplate.replaceAll("&Modifiers", getMethodModifiers( method )) .replaceAll("&ReturnType", getMethodReturnType( method )) .replaceAll("&MethodName", method.getName()) .replaceAll("&Parameters", getMethodParameters( method )) .replaceAll("&Throwables", getMethodThrowables( method )); // 方法声明以及实现 String body = bodyTemplate.replaceAll("&Declaration", declare ) .replaceAll("&Body", getMethodEntity( method )); |
清单 4. ProxyEx 的静态方法 getMethodEntity()
private static String getMethodEntity( Method method ) { String template = "\n{" + "\n java.lang.reflect.Method method = null;" + "\n try{" + "\n method = &Class.getMethod( \"&MethodName\", &ParameterTypes );" + "\n }" + "\n catch(Exception e){" + "\n }" + "\n Object r = null;" + "\n try{" + "\n r = handler.invoke( this, method, &ParameterValues );" + "\n }&Exceptions" + "\n &Return" + "\n}"; String result = template.replaceAll("&MethodName", method.getName() ) .replaceAll("&Class", method.getDeclaringClass().getName() + ".class") .replaceAll("&ParameterTypes", getMethodParameterTypesHelper(method)) .replaceAll("&ParameterValues", getMethodParameterValuesHelper(method) ) .replaceAll("&Exceptions", getMethodParameterThrowablesHelper(method)) .replaceAll("&Return", getMethodReturnHelper( method ) ); return result; } |
清单 5. ProxyEx 的静态方法 getTypeHelper()
private static String getTypeHelper(Class type) { if( type.isArray() ) { Class c = type.getComponentType(); return getTypeHelper(c) + "[]"; } else { return type.getName(); } } |
生成代理类的构造函数
相信读者依然清晰记得代理类是通过其构造函数反射生成的,而构造时传入的唯一参数就是调用处理器对象。为了保持与原代理机制的一致性,新的代理类的构造函数也同样只有一个调用处理器对象作为参数。模板简单如下
清单 6. 构造函数模板
public &Constructor(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; } |
生成整个代理类
通过以上步骤,构造函数和所有需被代理的方法的代码已经生成,接下来就是生成整个代理类的时候了。这个过程也很直观,通过获取相关信息并对类模板中各个标签位进行替换,便可以轻松的完成整个代理类的代码生成。
回页首
被遗忘的角落:类变量
等等,似乎遗忘了什么?从调用者的角度出发,我们希望代理类能够作为被代理类的如实代表呈现在用户面前,包括其内部状态,而这些状态通常是由类变量所体现出来的,于是就涉及到类变量的代理问题。
要解决这个问题,首先需要思考何时两者的类变量可能出现不一致?回答了这个问题,也就找到了解决思路。回顾代理类的构造函数,我们以粗糙的方式构造了代理类实例。它们可能一开始就已经不一致了。还有每次方法调用也可能导致被两者的类变量的不一致。如何解决?直观的想法是:1)构造时需设法进行同步;2)方法调用之前和之后也需设法进行同步。这样,我们就能够有效避免代理类和被代理类的类变量不一致的问题的出现了。
但是,如何获得被代理类的实例呢?从当前的的设计中已经没有办法做到。既然如此,那就继续我们的扩展之旅。只不过这次扩展的对象是调用处理器接口,我们将在扩展后的接口里加入获取被代理类对象的方法,且扩展调用处理器接口将以 static 和 public 的形式被定义在 ProxyEx 类中。
清单 7. ProxyEx 类内的静态接口 InvocationHandlerEx
public static interface InvocationHandlerEx extends InvocationHandler { // 返回指定 stubClass 参数所对应的被代理类实体对象 Object getStub(Class stubClass); } |
清单 8. 更新后的方法模板(部分)
Object r = null; try{ // 代理类到被代理类方向的变量同步 sync(&Class, true); r = handler.invoke( this, method, &ParameterValues ); // 被代理类到代理类方向的变量同步 sync(&Class, false); }&Exceptions &Return |
清单 9. 更新后的构造函数模板
public &Name(java.lang.reflect.InvocationHandler handler) { super(&Parameters); this.handler = handler; // 被代理类到代理类方向的变量同步 sync(null, false); } |
清单 10. 声明在动态生成的代理类内部的 snyc 函数
private synchronized void sync(java.lang.Class clazz, boolean toStub) { // 判断是否为扩展调用处理器 if( handler instanceof InvocationHandlerEx ) { java.lang.Class superClass = this.getClass().getSuperclass(); java.lang.Class stubClass = ( clazz != null ? clazz : superClass ); // 通过扩展调用处理器获得stub对象 Object stub = ((InvocationHandlerEx)handler).getStub(stubClass); if( stub != null ) { // 获得所有需同步的类成员列表,遍历并同步 java.lang.reflect.Field[] fields = getFields(superClass); for(int i=0; fields!=null&&i<fields.length; i++) { try { fields[i].setAccessible(true); // 执行代理类和被代理类的变量同步 if(toStub) { fields[i].set(stub, fields[i].get(this)); } else { fields[i].set(this, fields[i].get(stub)); } } catch(Throwable e) { } } } } } |
清单 11. 增加了静态 fieldsMap 变量后的类模板
package &Package; final public class &Name &Extends &Implements { private static java.util.HashMap fieldsMap = new java.util.HashMap(); private java.lang.reflect.InvocationHandler handler = null; &Constructors &Methods } |
private static java.lang.reflect.Field[] getFields(java.lang.Class c) { if( fieldsMap.containsKey(c) ) { return (java.lang.reflect.Field[])fieldsMap.get(c); } java.lang.reflect.Field[] fields = null; if( c == java.lang.Object.class ) { fields = c.getDeclaredFields(); } else { java.lang.reflect.Field[] fields0 = getFields(c.getSuperclass()); java.lang.reflect.Field[] fields1 = c.getDeclaredFields(); fields = new java.lang.reflect.Field[fields0.length + fields1.length]; System.arraycopy(fields0, 0, fields, 0, fields0.length); System.arraycopy(fields1, 0, fields, fields0.length, fields1.length); } fieldsMap.put(c, fields); return fields; } |
动态编译及装载
代码生成以后,需要经过编译生成 JVM 所能识别的字节码,而字节码还需要通过类装载器载入 JVM 才能最终被真正使用,接下来我们将阐述如何动态编译及装载。
首先是动态编译。这部分由 ProxyEx 类的 getProxyClassCodeSource 函数完成。该函数分三步进行:第一步保存源代码到 .java 文件;第二步编译该 .java 文件;第三步从输出的 .class 文件读取字节码。
清单 13. ProxyEx 的静态方法 getProxyClassCodeSource
private static byte[] getProxyClassCodeSource( String pkg, String className, String declare ) throws Exception { // 将类的源代码保存进一个名为类名加“.java”的本地文件 File source = new File(className + ".java"); FileOutputStream fos = new FileOutputStream( source ); fos.write( declare.getBytes() ); fos.close(); // 调用com.sun.tools.javac.Main类的静态方法compile进行动态编译 int status = com.sun.tools.javac.Main.compile( new String[] { "-d", ".", source.getName() } ); if( status != 0 ) { source.delete(); throw new Exception("Compiler exit on " + status); } // 编译得到的字节码将被输出到与包结构相同的一个本地目录,文件名为类名加”.class” String output = "."; int curIndex = -1; int lastIndex = 0; while( (curIndex=pkg.indexOf('.', lastIndex)) != -1 ) { output = output + File.separator + pkg.substring( lastIndex, curIndex ); lastIndex = curIndex + 1; } output = output + File.separator + pkg.substring( lastIndex ); output = output + File.separator + className + ".class"; // 从输出文件中读取字节码,并存入字节数组 File target = new File(output); FileInputStream f = new FileInputStream( target ); byte[] codeSource = new byte[(int)target.length()]; f.read( codeSource ); f.close(); // 删除临时文件 source.delete(); target.delete(); return codeSource; } |
清单 14. ProxyEx 的静态方法 defineClassHelper
private static Class defineClassHelper( String pkg, String cName, byte[] codeSource ) throws Exception { Method defineClass = Proxy.class.getDeclaredMethod( "defineClass0", new Class[] { ClassLoader.class, String.class, byte[].class, int.class, int.class } ); defineClass.setAccessible(true); return (Class)defineClass.invoke( Proxy.class, new Object[] { ProxyEx.class.getClassLoader(), pkg.length()==0 ? cName : pkg+"."+cName, codeSource, new Integer(0), new Integer(codeSource.length) } ); } |
性能改进
原动态代理机制中对接口数组有一些有趣的特点,其中之一就是接口的顺序差异会在一定程度上导致生成新的代理类,即使其实并无必要。其中的原因就是因为缓存表是以接口名称列表作为关键字,所以不同的顺序就意味着不同的关键字,如果对应的关键字不存在,就会生成新但是作用重复的代理类。在 ProxyEx 类中,我们通过主动排序避免了类似的问题,提高动态生成代理类的效率。而且,如果发现数组中都是接口类型,则直接调用父类 Proxy 的静态方法 getProxyClass 生成代理类,否则才通过扩展动态代理机制生成代理类,这样也一定程度上改进了性能。
回页首
兼容性问题
接下来需要考虑的是与原代理机制的兼容性问题。曾记否,Proxy 中还有两个静态方法:isProxyClass 和 getInvocationHandler,分别被用于判断 Class 对象是否是动态代理类和从 Object 对象获取对应的调用处理器(如果可能的话)。
清单 15. Proxy 的静态方法 isProxyClass 和 getInvocationHandler
static boolean isProxyClass(Class cl) static InvocationHandler getInvocationHandler(Object proxy) |
清单 16. ProxyEx 的静态方法 addProxyClass
private static void addProxyClass( Class proxy ) throws IllegalArgumentException { try { // 通过反射获取父类的私有 proxyClasses 变量并更新 Field proxyClasses = Proxy.class.getDeclaredField("proxyClasses"); proxyClasses.setAccessible(true); ((Map)proxyClasses.get(Proxy.class)).put( proxy, null ); } catch(Exception e) { throw new IllegalArgumentException(e.toString()); } } |
清单 17. ProxyEx 的静态方法 getInvocationHandler
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException { // 如果Proxy实例,直接调父类的方法 if( proxy instanceof Proxy ) { return Proxy.getInvocationHandler( proxy ); } // 如果不是代理类,抛异常 if( !Proxy.isProxyClass( proxy.getClass() )) { throw new IllegalArgumentException("Not a proxy instance"); } try { // 通过反射获取扩展代理类的调用处理器对象 Field invoker = proxy.getClass().getDeclaredField("handler"); invoker.setAccessible(true); return (InvocationHandler)invoker.get(proxy); } catch(Exception e) { throw new IllegalArgumentException("Suspect not a proxy instance"); } } |
坦言:也有局限
受限于 Java 的类继承机制,扩展的动态代理机制也有其局限,它不能支持:
声明为 final 的类;
声明为 final 的函数;
构造函数均为 private 类型的类;
回页首
实例演示
阐述了这么多,相信读者一定很想看一下扩展动态代理机制是如何工作的。本文最后将以 2010 世博门票售票代理为模型进行演示。
首先,我们定义了一个售票员抽象类 TicketSeller。
清单 18. TicketSeller
public abstract class TicketSeller { protected String theme; protected TicketSeller(String theme) { this.theme = theme; } public String getTicketTheme() { return this.theme; } public void setTicketTheme(String theme) { this.theme = theme; } public abstract int getTicketPrice(); public abstract int buy(int ticketNumber, int money) throws Exception; } |
清单 19. Expo2010TicketSeller
public class Expo2010TicketSeller extends TicketSeller { protected int price; protected int numTicketForSale; public Expo2010TicketSeller() { super("World Expo 2010"); this.price = 180; this.numTicketForSale = 200; } public int getTicketPrice() { return price; } public int buy(int ticketNumber, int money) throws Exception { if( ticketNumber > numTicketForSale ) { throw new Exception("There is no enough ticket available for sale, only " + numTicketForSale + " ticket(s) left"); } int charge = money - ticketNumber * price; if( charge < 0 ) { throw new Exception("Money is not enough. Still needs " + (-charge) + " RMB."); } numTicketForSale -= ticketNumber; return charge; } } |
清单 20. TicketBuyer
public class TicketBuyer { public static void main(String[] args) { // 创建真正的TickerSeller对象,作为stub实体 final TicketSeller stub = new Expo2010TicketSeller(); // 创建扩展调用处理器对象 InvocationHandler handler = new InvocationHandlerEx() { public Object getStub(Class stubClass) { // 仅对可接受的Class类型返回stub实体 if( stubClass.isAssignableFrom(stub.getClass()) ) { return stub; } return null; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object o; try { System.out.println(" >>> Enter method: " + method.getName() ); o = method.invoke(stub, args); } catch(InvocationTargetException e) { throw e.getCause(); } finally { System.out.println(" <<< Exit method: " + method.getName() ); } return o; } }; // 通过ProxyEx构造动态代理 TicketSeller seller = (TicketSeller)ProxyEx.newProxyInstance( TicketBuyer.class.getClassLoader(), new Class[] {TicketSeller.class}, handler); // 显示代理类的类型 System.out.println("Ticket Seller Class: " + seller.getClass() + "\n"); // 直接访问theme变量,验证代理类变量在对象构造时同步的有效性 System.out.println("Ticket Theme: " + seller.theme + "\n"); // 函数访问price信息 System.out.println("Query Ticket Price..."); System.out.println("Ticket Price: " + seller.getTicketPrice() + " RMB\n"); // 模拟票务交易 buyTicket(seller, 1, 200); buyTicket(seller, 1, 160); buyTicket(seller, 250, 30000); // 直接更新theme变量 System.out.println("Updating Ticket Theme...\n"); seller.theme = "World Expo 2010 in Shanghai"; // 函数访问theme信息,验证扩展动态代理机制对变量同步的有效性 System.out.println("Query Updated Ticket Theme..."); System.out.println("Updated Ticket Theme: " + seller.getTicketTheme() + "\n"); } // 购票函数 protected static void buyTicket(TicketSeller seller, int ticketNumber, int money) { try { System.out.println("Transaction: Order " + ticketNumber + " ticket(s) with " + money + " RMB"); int charge = seller.buy(ticketNumber, money); System.out.println("Transaction: Succeed - Charge is " + charge + " RMB\n"); } catch (Exception e) { System.out.println("Transaction: Fail - " + e.getMessage() + "\n"); } } } |
清单 21. 执行输出
Ticket Seller Class: class com.demo.proxy.test.TicketSellerProxy0 Ticket Theme: World Expo 2010 Query Ticket Price... >>> Enter method: getTicketPrice <<< Exit method: getTicketPrice Ticket Price: 180 RMB Transaction: Order 1 ticket(s) with 200 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Succeed - Charge is 20 RMB Transaction: Order 1 ticket(s) with 160 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - Money is not enough. Still needs 20 RMB. Transaction: Order 250 ticket(s) with 30000 RMB >>> Enter method: buy <<< Exit method: buy Transaction: Fail - There is no enough ticket available for sale, only 199 ticket(s) left Updating Ticket Theme... Query Updated Ticket Theme... >>> Enter method: getTicketTheme <<< Exit method: getTicketTheme Updated Ticket Theme: World Expo 2010 in Shanghai |
“Java 动态代理机制分析及扩展,第 1 部分”:阅读本系列的第一部分。
“Dynamic Proxy Classes”:查看 Java 动态代理的相关文档。
“Introduction to Java Exception Handling”:介绍了如何处理 Java 异常。
“Java 理论与实践: 用动态代理进行修饰”(developerWorks,2005 年 9 月):动态代理工具 是 java.lang.reflect 包的一部分,在 JDK 1.3 版本中添加到 JDK,它允许程序创建 代理对象。本文中,作者介绍了几个用于动态代理的应用程序。
“利用动态代理的 Java 验证”(developerWorks,2004 年 9 月):本文向您展示动态代理如何让核心应用程序代码独立于验证例程,而只关注业务逻辑。
developerWorks Java 技术专区:数百篇关于 Java 编程各个方面的文章。
相关文章推荐
- Java 动态代理机制分析及扩展,第 2 部分
- Java 动态代理机制分析和扩展,第 2 部分
- Java 动态代理机制分析及扩展,第 2 部分(转)
- Java 动态代理机制分析及扩展,第 2 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分(转)
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展,第 1 部分
- Java 动态代理机制分析及扩展
- Java 动态代理机制分析及扩展
- Java 动态代理机制分析及扩展