“字节码生成”并不是什么高深的技术,读者在看到“字节码生成”这个标题时也先不必去想诸如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是如何做到的。
-
public class DynamicProxyTest { -
interface IHello { -
void sayHello(); -
} -
static class Hello implements IHello { -
@Override -
public void sayHello() { -
System.out.println("hello world"); -
} -
} -
static class DynamicProxy implements InvocationHandler { -
Object originalObj; -
Object bind(Object originalObj) { -
this.originalObj = originalObj; -
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this); -
} -
@Override -
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { -
System.out.println("welcome"); -
return method.invoke(originalObj, args); -
} -
} -
public static void main(String[] args) { -
IHello hello = (IHello) new DynamicProxy().bind(new Hello()); -
hello.sayHello(); -
} -
}
运行结果如下:
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文件,反编译后可以看见如下所示的内容。
-
package org.fenixsoft.bytecode; -
import java.lang.reflect.InvocationHandler; -
import java.lang.reflect.Method; -
import java.lang.reflect.Proxy; -
import java.lang.reflect.UndeclaredThrowableException; -
final class $Proxy0 extends Proxy -
implements DynamicProxyTest.IHello -
{ -
private static Method m1; -
private static Method m3; -
private static Method m2; -
private static Method m0; -
public $Proxy0(InvocationHandler paramInvocationHandler) -
throws -
{ -
super(paramInvocationHandler); -
} -
public final boolean equals(Object paramObject) -
throws -
{ -
try -
{ -
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); -
} -
catch (Error|RuntimeException localError) -
{ -
throw localError; -
} -
catch (Throwable localThrowable) -
{ -
throw new UndeclaredThrowableException(localThrowable); -
} -
} -
public final void sayHello() -
throws -
{ -
try -
{ -
this.h.invoke(this, m3, null); -
return; -
} -
catch (Error|RuntimeException localError) -
{ -
throw localError; -
} -
catch (Throwable localThrowable) -
{ -
throw new UndeclaredThrowableException(localThrowable); -
} -
} -
public final String toString() -
throws -
{ -
try -
{ -
return (String)this.h.invoke(this, m2, null); -
} -
catch (Error|RuntimeException localError) -
{ -
throw localError; -
} -
catch (Throwable localThrowable) -
{ -
throw new UndeclaredThrowableException(localThrowable); -
} -
} -
public final int hashCode() -
throws -
{ -
try -
{ -
return ((Integer)this.h.invoke(this, m0, null)).intValue(); -
} -
catch (Error|RuntimeException localError) -
{ -
throw localError; -
} -
catch (Throwable localThrowable) -
{ -
throw new UndeclaredThrowableException(localThrowable); -
} -
} -
static -
{ -
try -
{ -
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); -
m3 = Class.forName("org.fenixsoft.bytecode.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]); -
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); -
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); -
return; -
} -
catch (NoSuchMethodException localNoSuchMethodException) -
{ -
throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); -
} -
catch (ClassNotFoundException localClassNotFoundException) -
{ -
throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); -
} -
} -
}
这个代理类的实现代码也很简单,他为传入接口中的每一个方法,以及从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的源码。
本文探讨了字节码生成技术,通过分析JDK的动态代理机制,深入理解字节码生成的过程及其在程序中的应用。以动态代理为例,展示了如何在运行时生成字节码,实现对原始类方法的增强。

1198

被折叠的 条评论
为什么被折叠?



