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处理字节码的方式是,拆分-修改-合并
思路是
- 将文件拆分成多个部分
- 对某一个部分的信息进行修改
- 将多个部分重新组织称一个新的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 类型 | 类型描述符 |
---|---|
boolean | Z |
char | C |
byte | B |
short | S |
int | I |
float | F |
long | J |
double | D |
Object | Ljava/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; |