zoukankan      html  css  js  c++  java
  • 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框架
  • 相关阅读:
    spring-mvc-继续学习
    springMVC学习
    spring-jdbc及事务
    Spring-MVC配置思路
    spring入门-注解的使用
    spring入门
    Spring MVC——数据校验(分组校验)
    Spring MVC——数据校验(数据回显)
    Spring MVC——数据检验步骤
    Spring MVC——参数装填方式
  • 原文地址:https://www.cnblogs.com/strongmore/p/13449590.html
Copyright © 2011-2022 走看看