JVM:字节码生成技术与动态代理的实现。

本文探讨了字节码生成技术,通过分析JDK的动态代理机制,深入理解字节码生成的过程及其在程序中的应用。以动态代理为例,展示了如何在运行时生成字节码,实现对原始类方法的增强。

 

“字节码生成”并不是什么高深的技术,读者在看到“字节码生成”这个标题时也先不必去想诸如Javassist、CGLib、ASM值类的字节码类库,因为JDK里面的javac命令就是字节码生成技术的“老祖宗”,并且javac也是一个由Java语言写成的程序,他的代码存放在OpenJDK的langtools/src/share/classes/com/sun/tools/javac目录中。要深入了解字节码生成,阅读javac和源码是个很好的途径。不过Javac对于我们这个例子来说太过庞大了。在Java里面除了javac和字节码类库外,使用字节码生成的例子还有很多,如Web服务器中的JSP编译器,编译时植入的AOP框架,还有很常用的动态代理技术,甚至在使用反射的时候虚拟机都有可能会在运行时生成字节码来提高执行速度。我们选择其中相对简单的动态代理来看看字节码生成技术是如何影响程序运作的。

相信许多Java开发人员都是用过动态代理,即时没有直接使用过java.lang.reflect.Proxy或实现过java.lang.reflect.InvocationHandler接口,应该也用过Spring来做过Bean的组织管理。如果使用过Spring,那大多数情况都会用过动态代理,因为如果Bean是面向接口编程,那么在Spring内部都是通过动态代理的方式来对Bean进行增强的。动态代理中所谓的“动态”,是针对使用Java代码实际编写了代理类的“静态”代理而言的,他的优势不在于省去了编写代码类那一点工作量,而是实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以很灵活的重用于不同的应用场景之中。
下面代码演示了一个最简单的动态代理的用法,原始的逻辑是打印一句“hello world”,代理类的逻辑是在原始类方法执行前打印一句“welcome”。我们先看一下代码,然后再分析JDK是如何做到的。


 
  1. public class DynamicProxyTest {

  2. interface IHello {

  3. void sayHello();

  4. }

  5. static class Hello implements IHello {

  6. @Override

  7. public void sayHello() {

  8. System.out.println("hello world");

  9. }

  10. }

  11. static class DynamicProxy implements InvocationHandler {

  12. Object originalObj;

  13. Object bind(Object originalObj) {

  14. this.originalObj = originalObj;

  15. return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);

  16. }

  17. @Override

  18. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  19. System.out.println("welcome");

  20. return method.invoke(originalObj, args);

  21. }

  22. }

  23. public static void main(String[] args) {

  24. IHello hello = (IHello) new DynamicProxy().bind(new Hello());

  25. hello.sayHello();

  26. }

  27. }

运行结果如下:

welcome
hello world

上述代码里,唯一的“黑匣子”就是Proxy.newProxyInstance()方法,除此之外再没有任何特殊之处。这个方法返回一个实现了IHello接口,并且代理了new Hello()实例行为的对象。跟踪这个方法的源码,可以看到程序进行了验证、优化、缓存、同步、生成字节码、显示类加载等操作,前面的步骤并不是我们关注的重点,而最后他调用了sun.misc.ProxyGenerator.generateProxyClass()方法来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码byte[]数组。如果想看一看这个在运行时产生的代理类中写了些什么,可以在main()方法中加入下面这句:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

加入这句代码后再次运行程序,磁盘中将会产生一个名为“$Proxy0.class”的代理类Class文件,反编译后可以看见如下所示的内容。


 
  1. package org.fenixsoft.bytecode;

  2.  
  3. import java.lang.reflect.InvocationHandler;

  4. import java.lang.reflect.Method;

  5. import java.lang.reflect.Proxy;

  6. import java.lang.reflect.UndeclaredThrowableException;

  7.  
  8. final class $Proxy0 extends Proxy

  9. implements DynamicProxyTest.IHello

  10. {

  11. private static Method m1;

  12. private static Method m3;

  13. private static Method m2;

  14. private static Method m0;

  15.  
  16. public $Proxy0(InvocationHandler paramInvocationHandler)

  17. throws

  18. {

  19. super(paramInvocationHandler);

  20. }

  21.  
  22. public final boolean equals(Object paramObject)

  23. throws

  24. {

  25. try

  26. {

  27. return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();

  28. }

  29. catch (Error|RuntimeException localError)

  30. {

  31. throw localError;

  32. }

  33. catch (Throwable localThrowable)

  34. {

  35. throw new UndeclaredThrowableException(localThrowable);

  36. }

  37. }

  38.  
  39. public final void sayHello()

  40. throws

  41. {

  42. try

  43. {

  44. this.h.invoke(this, m3, null);

  45. return;

  46. }

  47. catch (Error|RuntimeException localError)

  48. {

  49. throw localError;

  50. }

  51. catch (Throwable localThrowable)

  52. {

  53. throw new UndeclaredThrowableException(localThrowable);

  54. }

  55. }

  56.  
  57. public final String toString()

  58. throws

  59. {

  60. try

  61. {

  62. return (String)this.h.invoke(this, m2, null);

  63. }

  64. catch (Error|RuntimeException localError)

  65. {

  66. throw localError;

  67. }

  68. catch (Throwable localThrowable)

  69. {

  70. throw new UndeclaredThrowableException(localThrowable);

  71. }

  72. }

  73.  
  74. public final int hashCode()

  75. throws

  76. {

  77. try

  78. {

  79. return ((Integer)this.h.invoke(this, m0, null)).intValue();

  80. }

  81. catch (Error|RuntimeException localError)

  82. {

  83. throw localError;

  84. }

  85. catch (Throwable localThrowable)

  86. {

  87. throw new UndeclaredThrowableException(localThrowable);

  88. }

  89. }

  90.  
  91. static

  92. {

  93. try

  94. {

  95. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

  96. m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);

  97. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

  98. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

  99. return;

  100. }

  101. catch (NoSuchMethodException localNoSuchMethodException)

  102. {

  103. throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

  104. }

  105. catch (ClassNotFoundException localClassNotFoundException)

  106. {

  107. throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

  108. }

  109. }

  110. }

这个代理类的实现代码也很简单,他为传入接口中的每一个方法,以及从java.lang.Object中继承来的equals()、hashCode()、toString()方法都生成了对应的实现,并且统一调用了InvocationHandler对象的invoke()方法(代码中的“this.h”就是父类Proxy中保存的InvocationHandler实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。
这个例子中并没有讲到generateProxyClass()方法具体是如何产生代理类“$Proxy0.class”的字节码的,大致的生成过程其实就是根据Class文件的格式规范去拼装字节码,但在实际开发中,以byte为单位直接拼装出字节码的应用场合很少见,这种生成方式也只能产生一些高度模板化的代码。对于用户的程序代码来说,如果有要大量操作字节码的需求,还是使用封装好的字节码类库比较合适。如果读者对动态代理的字节码拼装过程很感兴趣,可以在OpenJDK的jdk/src/share/classes/sun/misc目录下找到sun.misc.ProxyGenerator的源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值