ASM技术

如果用反射的方式去获取类的注解,那么需要把所有路径下的class文件加入到jvm中解析,消耗巨大

spring中的ASM使用主要就是visotor(访问者)模式,直接先读取原来的文件,相当于reader,然后通过adapter,自己定制一套写法,最后再写入到一个新文件中,相当于改变了原有代码,增加了切面,

asm是什么

ASM是一个JAVA字节码操控框架.它能被用来动态生成类或者增加既有类的功能.ASM可以直接产生二进制class文件,也可以在类被载入java虚拟机之前动态改变行为.java class被存储在严格定义的.class文件里.这些类文件拥有足够的元数据来解析类中的所有元素,类名,方法名,属性,以及java字节码.ASM从类文件读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类.说白了ASM直接通过字节码来修改class文件(俗称字节码插装)

具体来说,Spring的AOP,可以通过JDK的动态代理来实现,也可以通过CGLIB实现。其中,CGLib (Code Generation Library)是在ASM的基础上构建起来的(当然JDK Proxy也是一样的),所以,Spring AOP是间接的使用了ASM。

ASM不是什么的缩写,那名称是怎么来的呢?一般而言在c语言中通常会有一个asm类,约定俗成的在里面写一些汇编语言

ASM 跟AOP三剑客的关系

APT、aspectJ、Javassit有什么关系?

APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件

aspectJ:AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的[编译器]用来生成遵守Java字节编码规范的Class文件。适合在某一个方法前后插入部分代码,处理某些逻辑:比如方法运行时间、插入动态权限检查等。问题会造成很多的冗余代码,产生很多代理类。简单来说就是在生成class时动态织入代码

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶滋)所创建的。简单来说就是源码级别的api去修改字节码

ASM如何处理字节码(ByteCode)数据

ASM处理字节码的方式是,拆分-修改-合并

思路是

  1. 将文件拆分成多个部分
  2. 对某一个部分的信息进行修改
  3. 将多个部分重新组织称一个新的class文件

API

Core API

ASM Core APi可以类比XML文件中SAX方式,不需要把这个类的整个结构读取出来,就可以用流式的方法来处理字节码文件.好处是非常节约内存,但是编程难度较大.然而出于性能考虑,一般情况下编程都是用Core Api 其中包含的几个关键类如下

  • ClassReader:用于读取class文件
  • ClassWriter:用于修改class文件,如修改类名,属性以及方法;或生成新的class文件
  • ClassVisitor:抽象类,可以对注解变量方法解析,对应的具体子类分别是AnnotationVisitor,FieldVisitor,MethodVisitor(会解析方法上的注解,参数,代码等)

TreeApi

ASM three API可以类比解析XML文件中的DOM方式,把整个类的结构读取到内存中,缺点是消耗内存多,但编程比较简单.TreeApi不同于CoreApi,TreeApi通过各种Node类来映射字节码各个区域

使用

引入

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>9.3</version>
</dependency>

安装

代码

Human

package test3;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: tongck
 * @Date: 2022/10/26 9:12
 */
public class Human {

    private String name;

    protected int age;

    public static long no;

    public static final String GENDER = "male";

    public Human() {
    }

    public static void hello() {
        System.out.println("Hello world!");
    }

    public int sum(int a, int b) {
        long nanoTime = System.nanoTime();
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("Hello StringBuilder!");
        System.out.println(stringBuilder);
        List<String> list = new ArrayList<String>();
        return a + b;
    }

}

查看到的字节码为

// class version 52.0 (52)
// access flags 0x21
public class test3/Human {


  // access flags 0x2
  private Ljava/lang/String; name

  // access flags 0x4
  protected I age

  // access flags 0x9
  public static J no

  // access flags 0x19
  public final static Ljava/lang/String; GENDER = "male"

  // access flags 0x1
  public <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static hello()V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "Hello world!"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

  // access flags 0x1
  public sum(II)I
    INVOKESTATIC java/lang/System.nanoTime ()J
    LSTORE 3
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ASTORE 5
    ALOAD 5
    LDC "Hello StringBuilder!"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    POP
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 5
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 6
    ILOAD 1
    ILOAD 2
    IADD
    IRETURN
    MAXSTACK = 2
    MAXLOCALS = 7
}

CreateHuman

package test3;

import java.io.FileOutputStream;
import java.lang.reflect.Method;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * @Author: tongck
 * @Date: 2022/10/26 9:38
 */
public class CreateHuman implements Opcodes {

    public static void main(String[] args) throws Exception {
        byte[] bytes = generateBytes();
        // 生成class
        String path = CreateHuman.class.getResource("/").getPath() + "test3/Human.class";
        System.out.println("输出路径:" + path);
        try (FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(bytes);
        }
        // 下面来验证生成的class是否能正常执行里面的代码
        // 加载Class
        Class<?> clazz = Class.forName("test3.Human");
        // 反射获取具体方法hello
        Method hello = clazz.getMethod("hello");
        // 调用具体方法
        Object helloResult = hello.invoke(clazz.getDeclaredConstructor().newInstance());
        System.out.println(helloResult);
        // 反射获取具体方法sum
        Method sum = clazz.getMethod("sum", int.class, int.class);
        // 调用具体方法
        Object sumResult = sum.invoke(clazz.getDeclaredConstructor().newInstance(), 1, 2);
        System.out.println(sumResult);
    }

    public static byte[] generateBytes() {
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;
        // 新建一个类生成器,COMPUTE_FRAMES这个参数能够让asm自动计算操作数栈
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        // 指定是jdk1.8版本;类的修饰符是public;类的完整类名org/example/asm8/Human;没有signature,这个用来表示泛型信息;所继承的父类是Object;没有实现接口
        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "test3/Human", null, "java/lang/Object", null);

        // 1.生成private String name;
        fieldVisitor = classWriter.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
        fieldVisitor.visitEnd();
        // 2.生成protected int age;
        fieldVisitor = classWriter.visitField(ACC_PROTECTED, "age", "I", null, null);
        fieldVisitor.visitEnd();
        // 3.生成public static long no;
        fieldVisitor = classWriter.visitField(ACC_PUBLIC | ACC_STATIC, "no", "J", null, null);
        fieldVisitor.visitEnd();
        // 4.生成public static final String GENDER = "male";
        fieldVisitor = classWriter.visitField(ACC_PUBLIC | ACC_FINAL | ACC_STATIC, "GENDER", "Ljava/lang/String;", null, "male");
        fieldVisitor.visitEnd();

        // 5.生成默认的构造方法:public Human()
        // <init>表示是构造方法,()V中()表示没有入参,V表示没有返回值是void方法
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        // 表示方法开始输入
        methodVisitor.visitCode();
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        methodVisitor.visitInsn(RETURN);
        // 设置操作数栈的深度和局部变量的大小
        // 这里因为已经设置了ClassWriter.COMPUTE_FRAMES,所以参数可以随便写,但必须调用这个方法
        methodVisitor.visitMaxs(0, 0);
        // 表示方法输出结束
        methodVisitor.visitEnd();

        // 6.生成静态方法:public static void hello()
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "hello", "()V", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        methodVisitor.visitLdcInsn("Hello world!");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        methodVisitor.visitInsn(RETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();

        // 7.生成方法:public int sum(int a, int b)
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "sum", "(II)I", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
        methodVisitor.visitVarInsn(LSTORE, 3);
        methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
        methodVisitor.visitVarInsn(ASTORE, 5);
        methodVisitor.visitVarInsn(ALOAD, 5);
        methodVisitor.visitLdcInsn("Hello StringBuilder!");
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
        methodVisitor.visitInsn(POP);
        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        methodVisitor.visitVarInsn(ALOAD, 5);
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
        methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
        methodVisitor.visitVarInsn(ASTORE, 6);
        methodVisitor.visitVarInsn(ILOAD, 1);
        methodVisitor.visitVarInsn(ILOAD, 2);
        methodVisitor.visitInsn(IADD);
        methodVisitor.visitInsn(IRETURN);
        methodVisitor.visitMaxs(0, 0);
        methodVisitor.visitEnd();
        classWriter.visitEnd();
        return classWriter.toByteArray();
    }
}

运行后正常输出

Hello world!
null
Hello StringBuilder!
3

注意

ClassWriter(ClassWriter.COMPUTE_FRAMES),这个ClassWriter的flag参数,建议设置COMPUTE_FRAMES,按照官网所说,性能虽然差点,但是可以自动更新操作数栈和方法调用帧计算,这时候methodVisitor.visitMaxs(0, 0)里面的参数就可以随便写了,但这个方法必须要调用,不然即使生成了class,在执行这个class中的方法时会报错。具体说明可以看jar中的注释。

Java 类型类型描述符
booleanZ
charC
byteB
shortS
intI
floatF
longJ
doubleD
ObjectLjava/lang/Object;
int[][I
Object[][][[Ljava/lang/Object;
源文件中的方法声明方法描述符
void m(int i, float f)(IF)V
int m(Object o)(Ljava/lang/Object;)I
int[] m(int i, String s)(ILjava/lang/String;)[I
Object m(int[] i)([I)Ljava/lang/Object;
Last modification:October 26, 2022
如果觉得我的文章对你有用,请随意赞赏