【进阶之路】动态代理与字节码生成

2022-03-07 13:48:27 浏览数 (1)

这段时间换了新工作,因为去了外企,所以对英语的要求突然猛增,现在每天靠着谷歌翻译过日子。在开会的时候,经常遇到不懂的单词,很多时候都需要记下读音,事后再慢慢根据语境去找对应的单词,日子过得可谓是有滋有味。于是乎,自我充电的时间大部分用来学习英语了,所以这段时间更新的节奏会很慢~

对于大多数Java程序员而言,我们会经常用到字节码生成与动态代理技术,比如编译时织入的AOP框架中,在Spring的Bean组织管理中,亦或是Web服务器的JSP编译器里。总之,我们在不知不觉中已经大量的用到了这些技术了。

动态代理中所说的动态,是基于Java代码实际编写代理类的静态代理而言的。相比较而言,它的优势在于可以在不知道原始类与接口的时候,就先确定了代理行为,可以很方便的在不同的应用场景中灵活运用。同时,还可以减少代码的行数,让你的代码更加美观和简洁。

一、动态代理

这边简单的实现一个动态代理的方法,如果想看基于AOP与注解形式的,可以去看我之前的文章,也讲的很详细【进阶之路】自定义注解介绍与实战。

代码语言:javascript复制
public class DynamicTest {
    public static void main(String[] args) {
        IPayment pay = (IPayment) new BankDynamicProxy().bind(new Alipay());
        pay.payment();
    }

    interface IPayment {
        void payment();
    }

    static class Alipay implements IPayment {
        @Override
        public void payment() {
            System.out.println("Use Alipay to payment");
        }
    }

    static class BankDynamicProxy implements InvocationHandler {
        Object dynamicProxy;

        Object bind(Object dynamicProxy) {
            this.dynamicProxy = dynamicProxy;
            return Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader()
                    , dynamicProxy.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("access Bank Api");
            return method.invoke(dynamicProxy, args);
        }
    }
}

这个动态代理方法逻辑很简单,就是在使用支付宝支付的时候去请求了一次银行的接口。通过这个方法,我们可以使用debug的方法看到程序的验证、优化、缓存、字节码生成、类加载等一些列操作

但是我们这一次不用去探究全部的流程,只需要去了解字节码生成的操作。

代码语言:javascript复制
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);

这个方法会在运行时生产一个描述代理类的字节码byte[]数组。

在debug中看实在是太麻烦,我们可以生成一个字节码文件,通过反编译来查看具体的内容。

二、字节码生成

只需要在代码的main方法中加入下图这个方法,就可以在指定的位置生成一个名为Proxy0.class 的代理类文件。当然,换成nanju.class也没问题。

代码语言:javascript复制
public static void main(String[] args) {

    byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", DynamicTest.class.getInterfaces());
    String path = "D:\temp\$Proxy0.class";
    try (FileOutputStream fos = new FileOutputStream(path)) {
        fos.write(classFile);
        fos.flush();
        System.out.println("Agent class file written successfully");
    } catch (Exception e) {
        System.out.println("file written fail");
    }
}

然后我们通过最简单的方法,直接把class文件,拖拽到IntelliJ IDEA工具中,IntelliJ自动反编译为Java文件。

代码语言:javascript复制
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy {
    private static Method m1;
    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 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"));
            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());
        }
    }
}

从这个动态代理类反编译的代码中可以看出,它为从Object中继承来的equals(),toString()和hashCode()等方法都生成了对应的实现,并且统一调用了java.lang.reflect.InvocationHandler对象中的invoke()方法,只是传入的参数和Method方法有所不同。所以无论动态代理什么方法,其实执行的依旧是InvocationHandler中的额逻辑。

generateProxyClass()方法通过Class文件的规范去拼接字节码,但是对于程序代码来说,这样的拼接很消耗资源且只能产生高度模板化的代码。比起这样的生成,现成的字节码库更适合于生产上的实践。

0 人点赞