AOP的底层实现--ASM

在ASM的核心组件中,Opcodes接口定义了一些常量,尤其是版本号、访问标识符、字节码等信息。ClassReader用于读取Class文件,它的作用时进行Class文件的解析,并可以接受一个ClassVisitor,ClassReader会将解析过程中产生的类的部分信息,比如访问提示符、字段、方法逐个送入ClassVisitor,Class Visitor在接受到对应的类信息后,可以进行各自的处理。

ClassVisitor有一个重要的子类为ClassWriter,它负责进行Class文件的输出和形成。ClassVisitor在进行字段和方法的处理的时候,会委托给FieldVisitor和MethodVisitor进行处理。因此在类的处理过程中,会创建相应的FieldVisitor和MethodVisitor对象。FieldVisitor和FieldVisitor也各自有一个重要的子类,FieldWriter和MethodWriter。当classWriter进行字段和方法的处理时,也是依赖这两个类进行的。

示例1--ASM入门helloworld

/**
 * author:yinweicheng
 */
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.InvocationTargetException;

/**
 * 继承classloader,方便立即被系统加载
 * 实现Opcodes,方便访问全局变量
 */
public class AsmHelloWorld extends ClassLoader implements Opcodes{
   /**
    *
    * @param args 程序参数
    * @throws InvocationTargetException 类加载异常
    * @throws IllegalAccessException 初始化实例失败
    */
   public static void main(final String[] args) throws InvocationTargetException, IllegalAccessException {
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
      cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
      //设置类的基本信息
      MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
      //生成example的构造函数
      mw.visitVarInsn(ALOAD, 0);
      mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
      mw.visitInsn(RETURN);
      mw.visitMaxs(0, 0);
      mw.visitEnd();
      //生成main主函数
      mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
      mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
      mw.visitLdcInsn("hello world!");
      mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
      mw.visitInsn(RETURN);
      mw.visitMaxs(0,0);
      mw.visitEnd();
      //生成class文件流的二进制表示
      byte[] code = cw.toByteArray();
      //将生成的文件流载入系统,并通过反射调用main方法
      AsmHelloWorld loader = new AsmHelloWorld();
      Class exampleClass = loader.defineClass("Example", code,0, code.length);
      exampleClass.getMethods()[0].invoke(null, new Object[]{null});
   }
}

既然我们可以通过asm生成字节码文件,我们当然能够通过asm重构class文件。例如当前有一个账户account,要想在不修改account的前提下,对用户先进行安全检查,然后在进行操作。

示例--asm实现面向切面编程

account:

 
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 *
 */
public class Account {
   /**
    *
    */
   public void operation() throws InterruptedException {
      Thread.sleep(1000);
      System.out.println("operation...");
   }
}

检查类:

/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 * class_name:SecurityChecker
 * usage: 对account进行安全检查
 **/
public class SecurityChecker {
   /**
    * 进行安全检查的方法
    *
    * @return 是否安全
    */
   public static boolean checkSecurity() {
      System.out.println("SecurityChecker.checkSercurity...");
      if ((System.currentTimeMillis() & 0x1) == 0) {
         System.out.println("check fail");
         return false;
      } else
         return true;
   }
}

重构方法:

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:AddSecurityCheckMethodAdapter
 * usage: 添加方法检查,将检查方法添加到运行方法上
 **/
public class AddSecurityCheckMethodAdapter extends MethodVisitor {
   /**
    * 继承构造方法
    *
    * @param mv :methodvisitor
    */
   public AddSecurityCheckMethodAdapter(final MethodVisitor mv) {
      super(Opcodes.ASM5, mv);
   }

   public void visitCode() {
      Label continueLabel = new Label();
      visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/asm/SecurityChecker", "checkSecurity", "()Z");
      visitJumpInsn(Opcodes.IFNE, continueLabel);
      visitInsn(Opcodes.RETURN);
      visitLabel(continueLabel);
      super.visitCode();
   }
}
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:AddSecurityCheckClassAdapter
 * useage: 添加类检查,重构运行方法
 **/
public class AddSecurityCheckClassAdapter extends ClassVisitor {
   /**
    * 继承构造方法
    *
    * @param classVisitor :定义类观察器
    */
   public AddSecurityCheckClassAdapter(final ClassVisitor classVisitor) {
      super(Opcodes.ASM5, classVisitor);
   }

   /**
    * @param access     : 访问标志
    * @param name       : 名称索引
    * @param desc       :描述符索引
    * @param signature  :标志位
    * @param exceptions :异常表
    * @return :返回MethodVisitor
    */
   public MethodVisitor visitMethod(final int access, final String name, final String desc,
                                    final String signature, final String[] exceptions) {
      MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
      MethodVisitor wrappedMv = mv;
      if (mv != null) {
         if (name.equals("operation")) {
            wrappedMv = new AddSecurityCheckMethodAdapter(mv);
         }
      }
      return wrappedMv;
   }
}
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * class_name:SecurityWeaveGenerator
 * usage:
 **/
public class SecurityWeaveGenerator {
   /**
    * 通过asm重构检查类account的class文件,IDEA的class文件路径与eclipse路径不同
    *
    * @param args args
    * @throws IOException 读写异常
    */
   public static void main(final String[] args) throws IOException {
      String classname = Account.class.getName();
      ClassReader cr = new ClassReader(classname);
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
      AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
      cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
      byte[] data = cw.toByteArray();
      File file = new File("out/production/asm/" + classname.replaceAll("\\.", "/") + ".class");
      FileOutputStream fout = new FileOutputStream(file);
      fout.write(data);
      fout.close();
   }
}

在对account的class文件进行重构后,在运行operation方法之前首先要运行checkSecurity方法,这样创建一个演示类运行operation方法查看输入结果。

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 * class_name:RunAccountMain
 * useage:
 **/
public class RunAccountMain {
   /**
    * @param args
    */
   public static void main(String[] args) {
      Account account = new Account();
      account.operation();
   }
}

输出结果:

D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=10556:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
SecurityChecker.checkSercurity...
operation...

Process finished with exit code 0

可以看到,在运行operation方法前首先运行了checkSecurity方法。相当于aop中的前置通知,那么怎么实现环绕通知呢?假设要对用户的操作时间进行统计,怎么在不修改account的基础上进行呢?

添加计算时间类:

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

/**
 * class_name:TimeStat
 * usage:
 **/
public class TimeStat {
   /**
    * 计时
    */
   static ThreadLocal<Long> t = new ThreadLocal<Long>();

   /**
    * 设置开始时间
    */
   public static void start() {
      t.set(System.currentTimeMillis());
   }

   /**
    *输出结束时间
    */
   public static void end() {
      long time = System.currentTimeMillis() - t.get();
      System.out.println(Thread.currentThread().getStackTrace()[2] + "spend:" + time);
   }
}
/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:TimeStatClassAdapter
 * usage:
 **/
public class TimeStatClassAdapter extends ClassVisitor {

   public TimeStatClassAdapter(ClassVisitor classVisitor) {
      super(Opcodes.ASM5, classVisitor);
   }

   public MethodVisitor visitMethod(final int access, final String name, final String desc,
                                    final String signature, final String[] exceptions) {
      MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
      MethodVisitor wrappedMv = mv;
      if (mv != null) {
         if (name.equals("operation")) {
            wrappedMv = new TimeStatMethodAdapter(mv);
         }
      }
      return wrappedMv;
   }
}
/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:TimeStatMethodAdapter
 * usage:
 **/
public class TimeStatMethodAdapter extends MethodVisitor implements Opcodes {
   public TimeStatMethodAdapter(MethodVisitor mv) {
      super(Opcodes.ASM5, mv);
   }

   public void visitCode() {
      visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "start", "()V");
      super.visitCode();
   }

   public void visitInsn(int opcode) {
      if (opcode >= IRETURN && opcode <= RETURN) {
         visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "end", "()V");
      }
      mv.visitInsn(opcode);
   }
}

修改weaveGenerate类,重新覆写class文件,运行后的输出结果为:

D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=11956:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
operation...
edu.hrbeu.asm.Account.operation(Unknown Source)spend:1000

Process finished with exit code 0

猜你喜欢

转载自blog.csdn.net/yinweicheng/article/details/81005608