ASM实现动态代理
介绍
ASM是一个java字节码操纵和分析框架,它可以直接以二进制的形式修改class或动态生成class。官网
使用
接下来我们使用asm框架实现一个和JDK动态代理同样的功能。
引入maven依赖
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>8.0.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>
定义目标对象接口
/**
* 可以唱歌的
*/
public interface Singable {
/**
* 唱歌
*/
void sing();
}
定义目标对象
/**
* 歌手
*/
public class Singer implements Singable {
@Override
public void sing() {
System.out.println("I am singing...");
}
}
定义自己的类加载器
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
super(Thread.currentThread().getContextClassLoader());
}
/**
* 将字节数组转化为Class对象
*
* @param name 类全名
* @param data class数组
* @return
*/
public Class<?> defineClassForName(String name, byte[] data) {
return this.defineClass(name, data, 0, data.length);
}
}
创建代理
/**
* ASM动态代理
*/
public class SingerAgentDump implements Opcodes {
// 生成一个class的字节数组
public static byte[] dump() throws Exception {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
RecordComponentVisitor recordComponentVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
// 定义class版本1.8,访问权限,类名,继承类,实现接口等信息
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/imooc/sourcecode/java/dynamicproxy/asm/test3/SingerAgent", null, "java/lang/Object", new String[]{"com/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable"});
classWriter.visitSource("SingerAgent.java", null);
{
// 定义私有属性
fieldVisitor = classWriter.visitField(ACC_PRIVATE, "delegate", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable;", null, null);
fieldVisitor.visitEnd();
}
{
// 定义构造器
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable;)V", null, null);
methodVisitor.visitParameter("delegate", 0);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(10, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(11, label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitFieldInsn(PUTFIELD, "com/imooc/sourcecode/java/dynamicproxy/asm/test3/SingerAgent", "delegate", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable;");
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(12, label2);
methodVisitor.visitInsn(RETURN);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLocalVariable("this", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/SingerAgent;", null, label0, label3, 0);
methodVisitor.visitLocalVariable("delegate", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable;", null, label0, label3, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
// 定义方法sing
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sing", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(16, label0);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("before sing...");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(17, label1);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/imooc/sourcecode/java/dynamicproxy/asm/test3/SingerAgent", "delegate", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable;");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "com/imooc/sourcecode/java/dynamicproxy/asm/test3/Singable", "sing", "()V", true);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(18, label2);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("after sing...");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(19, label3);
methodVisitor.visitInsn(RETURN);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLocalVariable("this", "Lcom/imooc/sourcecode/java/dynamicproxy/asm/test3/SingerAgent;", null, label0, label4, 0);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
// 将字节数组加载为class
public static Singable newProxyInstance(Singable delegate) throws Exception {
String className = "com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent";
byte[] classData = dump();
Class<?> aClass = new MyClassLoader().defineClassForName(className, classData);
return (Singable) aClass.getDeclaredConstructor(Singable.class).newInstance(delegate);
}
}
ASM提供了一个功能,可以将普通的java代码转换成ASM代码,上面的代码就是通过转换得到的。
定义要转换的java代码
/**
* 歌手经纪人
*/
public class SingerAgent implements Singable {
private Singable delegate;
public SingerAgent(Singable delegate) {
this.delegate = delegate;
}
@Override
public void sing() {
System.out.println("before sing...");
delegate.sing();
System.out.println("after sing...");
}
}
转换
public class Client {
public static void main(String[] args) throws IOException {
ASMifier.main(new String[]{SingerAgent.class.getName()});
}
}
客户端调用
public class Client {
public static void main(String[] args) throws Exception {
Singable singerAgent = SingerAgentDump.newProxyInstance(new Singer());
singerAgent.sing();
}
}
输出结果为
before sing...
I am singing...
after sing...
符合预期,说明我们通过ASM创建的class是可以正常工作的,但对比JDK创建动态代理,ASM涉及到大量对java底层字节码的操作,我们对字节码越熟悉,使用ASM就会越容易。
遇到的问题
// 将字节数组加载为class
public static SingerAgent newProxyInstance(Singable delegate) throws Exception {
String className = "com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent";
byte[] classData = dump();
Class<?> aClass = new MyClassLoader().defineClassForName(className, classData);
return (SingerAgent) aClass.getDeclaredConstructor(Singable.class).newInstance(delegate);
}
刚开始我将加载后的class强转为了SingerAgent 类,但报异常
Exception in thread "main" java.lang.ClassCastException: class com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent cannot be cast to class com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent (com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent is in unnamed module of loader com.imooc.sourcecode.java.dynamicproxy.asm.test3.MyClassLoader @4590c9c3; com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgent is in unnamed module of loader 'app')
at com.imooc.sourcecode.java.dynamicproxy.asm.test3.SingerAgentDump.newProxyInstance(SingerAgentDump.java:98)
at com.imooc.sourcecode.java.dynamicproxy.asm.test3.Client.main(Client.java:5)
原因是因为生成的SingerAgent 类和原有的SingerAgent 类不是同一个类加载器加载的,所以不能转换。
ASM使用场景
ASM是一个非常强大的字节码工具,以下都使用到了ASM
- the OpenJDK, to generate the lambda call sites, and also in the Nashorn compiler, OpenJDK 对lambda 的实现
- the Groovy compiler and the Kotlin compiler,Cobertura and Jacoco, to instrument classes in order to measure code coverage, Groovy和Kotlin的编译器
- CGLIB, to dynamically generate proxy classes (which are used in other projects such as Mockito and EasyMock), Cglib框架
- Gradle, to generate some classes at runtime. Gradle框架