【进阶之路】动态代理与字节码生成
这段时间换了新工作,因为去了外企,所以对英语的要求突然猛增,现在每天靠着谷歌翻译过日子。在开会的时候,经常遇到不懂的单词,很多时候都需要记下读音,事后再慢慢根据语境去找对应的单词,日子过得可谓是有滋有味。于是乎,自我充电的时间大部分用来学习英语了,所以这段时间更新的节奏会很慢~
对于大多数Java程序员而言,我们会经常用到字节码生成与动态代理技术,比如编译时织入的AOP框架中,在Spring的Bean组织管理中,亦或是Web服务器的JSP编译器里。总之,我们在不知不觉中已经大量的用到了这些技术了。
动态代理中所说的动态,是基于Java代码实际编写代理类的静态代理而言的。相比较而言,它的优势在于可以在不知道原始类与接口的时候,就先确定了代理行为,可以很方便的在不同的应用场景中灵活运用。同时,还可以减少代码的行数,让你的代码更加美观和简洁。
一、动态代理
这边简单的实现一个动态代理的方法,如果想看基于AOP与注解形式的,可以去看我之前的文章,也讲的很详细【进阶之路】自定义注解介绍与实战。
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的方法看到程序的验证、优化、缓存、字节码生成、类加载等一些列操作
但是我们这一次不用去探究全部的流程,只需要去了解字节码生成的操作。
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
这个方法会在运行时生产一个描述代理类的字节码byte[]数组。
在debug中看实在是太麻烦,我们可以生成一个字节码文件,通过反编译来查看具体的内容。
二、字节码生成
只需要在代码的main方法中加入下图这个方法,就可以在指定的位置生成一个名为\(Proxy0.class 的代理类文件。当然,换成\)nanju.class也没问题。
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文件。
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文件的规范去拼接字节码,但是对于程序代码来说,这样的拼接很消耗资源且只能产生高度模板化的代码。比起这样的生成,现成的字节码库更适合于生产上的实践。
有需要的同学可以加我的公众号,以后的最新的文章第一时间都在里面,也可以找我要思维导图