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框架
posted @ 2020-08-06 22:54  strongmore  阅读(1189)  评论(0编辑  收藏  举报