Skip to content

Java 字节码生成技术与JDK动态代理的实现

Published: at 16:51:50

简介

“字节码生成”并不是什么高深的技术,我们能想到的常用字节码类库有Javassist、CGLib、ASM等,其实JDK里面的javac命令才是字节码生成技术的“祖宗”。JDK8版本的javac的源代码在OpenJDK上可以看到。

使用字节码生成技术的例子有很多,比如Web服务器的JSP编译器,编译时植入的AOP框架,还有很多常用的动态代理技术,甚至使用反射时虚拟机也有可能会在运行时生成字节码来提升执行速度。

动态代理的实现

在JAVA中使用动态代理有两种常用的方式:一是使用JDK的原生动态代理,二是使用CGLib来实现动态代理。

这里主要说下JDK的动态代理实现。 动态代理所谓的“动态”,是针对使用JAVA代码实际编写了代理类的“静态”代理而言的,它的优势不是在于省去了编写代理类那一点的工作量,而是实现了可以在原始类和接口还未知的情况下,就能确定代理类的代理行为,当代理类与原始类脱离直接关系后,就可以很灵活地重用于不同的引用场景中

动态代理用法实例:

public class TestDynamicProxy{
    interface IHello{
        void sayHello();
    }
    static class Hello implements IHello{
        @Override
        public void sayHello(){
            System.out.println("Hello World!");
        }
    }
    static class DynamicProxy implements InvocationHandler{
        private Object object;
        public Object build(Object object){
            this.object = object;
            Class c = object.getClass();
            // 返回指定类的代理类示例
            return Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),this);
        }
        @Override
        public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
            // 代理类的代理行为,被代理类的行为未知情况下也可以对被代理类进行行为增强
            System.out.println("welcome");
            return method.invoke(object,args);
        }
    }

    public static void main(String[] args){
        IHello hello = (IHello) new DynamicProxy().build(new Hello());
        hello.sayHello();
    }
}

启动时加入命令:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以将运行时产生的动态代理类保存下来,最终磁盘中会保存一个名为“$Proxy0.class”的代理类Class文件。打开这个文件后可以看到以下内容:

final class $Proxy0 extends Proxy implements IHello {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("net.admol.jingling.demo.dynamic.TestDynamicProxy$IHello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从这个代理类可以看出,这个代理类被final 修饰, 继承了Proxy类(这也是为什么使用JDK代理时必须要用接口,而不能用普通类),里面有4个属性m0、m1、m2、m3,分别对应hashCode()、equals()、toString()、sayHello()四个方法。并且统一调用了supper.h.invoke() (super.h就是父类的InvocationHandler实例)方法,所以无论调用动态代理的哪一个方法,实际上都是在执行InvocationHandler.invoke方法中的代理逻辑。