ASM字节码技术介绍
ASM是一个通用的 Java 字节码操作和分析框架。它可用于修改现有类或动态生成类(直接以二进制形式)。ASM 提供了一些常见的字节码转换和分析算法,可从中构建自定义复杂转换和代码分析工具。ASM 提供与其他 Java 字节码框架类似的功能,但更注重性能。由于它的设计和实现尽可能小巧和快速,因此非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)。
官网: https://asm.ow2.io/
文档: https://asm.ow2.io/asm4-guide.pdf
JavaDoc: https://asm.ow2.io/javadoc/index.html
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>data-mapping</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<asm.version>9.7</asm.version>
</properties>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
代码组织介绍
本章翻译自官方文档: https://asm.ow2.io/developer-guide.html
ASM 主要分为以下几个包:
org.objectweb.asm
是核心包。定义了 ASM 的访问 API,还提供了ClassReader
和ClassWriter
类进行类文件数据的读取和写出。该包不依赖其他任何包。org.objectweb.asm.signature
提供读写泛型签名的 API。它是核心包的补充包。org.objectweb.asm.tree
在核心包提供的类似SAX的 API之上提供了类似DOM的 API 。它可用于实现复杂的类转换,而核心包对于此类转换的使用则过于复杂。org.objectweb.asm.tree.analysis
在 tree 包之上提供了一个静态字节码分析框架。除了 tree 包之外,它还可以用于实现真正复杂的类转换,这些转换需要知道每条指令的堆栈映射帧的状态。org.objectweb.asm.commons
提供了一些基于 core 和 tree 包的有用的类适配器。这些适配器可以直接使用,也可以扩展以实现更具体的类转换。org.objectweb.asm.util
提供一些有用的类访问者和适配器,可用于调试目的。运行时一般不需要。org.objectweb.asm.xml
已弃用。它提供了将类转换为 XML 或从 XML 转换为类的功能。
从实现的角度来看,核心包是最复杂的包。tree、util和 xml包非常简单(它们只是将类从一种高级表示转换为另一种高级表示,这比将类从其字节数组形式转换为高级表示形式或反之亦然要简单得多)。 包signature也相当简单(它包含一个解析器和一个用于小语法的漂亮打印机)。
主要数据结构
常用ASM操作
定义类
public class Main {
//本代码仅为示例,因此不进行异常的处理
public static void main(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
//该类为public
int access = Opcodes.ACC_PUBLIC;
//类名
String clazz = "org.cikaros.A"
//该类继承自Object.class
//该方法的作用是将 '.' 转换为 '/'
String superName = Util.packageToPath(Object.class);//当前值为 java/lang/Object
//该类实现了哪些接口
Class<?>[] interfaces = new Class<?>[] {Function.class,AutoCloseable.class};
//定义一个类
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, Util.packageToPath(clazz), null, superName, Arrays.stream(interfaces).map(Class::getTypeName).map(Util::packageToPath).toArray(String[]::new));
//TODO 编写其他业务逻辑
//生成字节码文件数据,将其保存为文件,即为字节码文件
cw.toByteArray();
}
}
定义属性
...
//TODO 编写其他业务逻辑
//该属性为public
int access = Opcodes.ACC_PUBLIC;
//属性名字
String field = "";
//类型描述
String descriptor = "";
//创建一个整数
//descriptor = Type.getDescriptor(int.class);
FieldVisitor fv = cw.visitField(access, field, descriptor, null, null);
fv.visitEnd();
...
定义方法
...
//TODO 编写其他业务逻辑
//该方法为public
int access = Opcodes.ACC_PUBLIC;
//方法名字
String method = "";
//方法签证描述
String descriptor = "";
//无参数和返回值
//descriptor = Type.getMethodDescriptor(Type.VOID_TYPE);
//存在参数和返回值 这里假设有一个类型为Object的参数,返回值为Boolean类型
//descriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE,Type.getType(Object.class));
MethodVisitor mv = CW.visitMethod(access, method, descriptor, null, null);
mv.visitCode();
//TODO 定义方法体
mv.visitEnd();
...
定义无参构造函数
//TODO 编写其他业务逻辑
//该方法为public
int access = Opcodes.ACC_PUBLIC;
//方法名字
String method = "<init>";
//方法签证描述
String descriptor = Type.getMethodDescriptor(Type.VOID_TYPE);
...
//TODO 定义方法体
//加载this引用
mv.visitVarInsn(Opcodes.ALOAD, 0);
//调用特殊方法 <init> (固定写法)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Util.packageToPath(Object.class.getTypeName()), method, "()V", false);
//返回
mv.visitInsn(Opcodes.RETURN);
//设置堆栈空间大小
mv.visitMaxs(1, 1);
...
定义getter
//TODO 编写其他业务逻辑
//该方法为public
int access = Opcodes.ACC_PUBLIC;
//属性名字 假设属性为A
String field = "a";
//属性类型
Class<?> fieldType = String.class;
//方法名字
String method = "getA";
//方法签证描述 假设类型为String
String descriptor = Type.getMethodDescriptor(Type.getType(fieldType));
...
//TODO 定义方法体
//加载this引用
mv.visitVarInsn(Opcodes.ALOAD, 0);
//获取属性
mv.visitMethodInsn(Opcodes.GETFIELD, clazz, Util.packageToPath(Object.class.getTypeName()), field, Type.getDescriptor(fieldType));
//返回属性引用
mv.visitInsn(Opcodes.ARETURN);
//设置堆栈空间大小
mv.visitMaxs(1, 1);
...
定义setter
//TODO 编写其他业务逻辑
//该方法为public
int access = Opcodes.ACC_PUBLIC;
//属性名字 假设属性为A
String field = "a";
//属性类型
Class<?> fieldType = String.class;
//方法名字
String method = "setA";
//方法签证描述 假设类型为String
String descriptor = Type.getMethodDescriptor(Type.getType(fieldType));
...
//TODO 定义方法体
//加载this引用
mv.visitVarInsn(Opcodes.ALOAD, 0);
//加载第一个参数
mv.visitVarInsn(Opcodes.ALOAD, 1);
//为属性赋值属性
mv.visitMethodInsn(Opcodes.PUTFIELD, Util.packageToPath(clazz), field, Type.getDescriptor(fieldType));
//返回
mv.visitInsn(Opcodes.RETURN);
//设置堆栈空间大小
mv.visitMaxs(2, 2);
...
附录
针对上述代码进行逻辑抽象。
package org.cikaros.mapping.core;
import lombok.Data;
import org.objectweb.asm.*;
import javax.annotation.Generated;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import static org.objectweb.asm.Opcodes.*;
public class ClassGenerator {
private static class Util {
/**
* 包路径转路径
*/
public static String packageToPath(String packageName) {
return packageName.replace('.', '/');
}
/**
* 首字母大写(进行字母的ascii编码前移,效率是最高的)
*/
public static String getMethodName(String fieldName) {
char[] chars = fieldName.toCharArray();
chars[0] = toUpperCase(chars[0]);
return String.valueOf(chars);
}
/**
* 字符转成大写
*/
public static char toUpperCase(char c) {
if (97 <= c && c <= 122) {
c ^= 32;
}
return c;
}
}
private final ClassWriter CW = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
public void clazz(String clazz, Class<?> supper, Class<?>[] interfaces) {
String superName = Util.packageToPath(supper.getTypeName());
CW.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, Util.packageToPath(clazz), null, superName, Arrays.stream(interfaces).map(Class::getTypeName).map(Util::packageToPath).toArray(String[]::new));
this.annotation(Generated.class,av->{
av.visit("comments", "Generated By org.cikaros.mapping.core.ClassGenerator");
av.visit("date", DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss:SSS").format(LocalDateTime.now()));
});
this.defaultConstructor();
}
private void defaultConstructor() {
this.defineMethod(Opcodes.ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), mv -> {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Util.packageToPath(Object.class.getTypeName()), "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
});
}
public void argsConstructor(String clazz, Map<String, Class<?>> args) {
List<Map.Entry<String, Class<?>>> entries = args.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toList());
//生成私有属性
entries.forEach(arg -> this.field(Opcodes.ACC_PRIVATE, arg.getValue(), arg.getKey()));
this.defineMethod(Opcodes.ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, entries.stream().map(Map.Entry::getValue).map(Type::getType).toArray(Type[]::new)), mv -> {
// 调用父类构造函数
mv.visitVarInsn(Opcodes.ALOAD, 0); // 加载 this
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Util.packageToPath(Object.class.getTypeName()), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false);
// 假设参数按顺序传递,从第二个参数开始(索引1)
int index = 1; // this 的索引是0,参数从1开始
for (Map.Entry<String, Class<?>> arg : entries) {
mv.visitVarInsn(Opcodes.ALOAD, 0); // 加载 this
// 根据参数类型加载参数到栈上
Class<?> argType = arg.getValue();
if (argType == int.class) {
mv.visitVarInsn(Opcodes.ILOAD, index);
} else if (argType == float.class) {
mv.visitVarInsn(Opcodes.FLOAD, index);
} else if (argType == double.class) {
mv.visitVarInsn(Opcodes.DLOAD, index);
} else if (argType == long.class) {
mv.visitVarInsn(Opcodes.LLOAD, index);
} else if (argType == short.class) {
mv.visitVarInsn(Opcodes.ILOAD, index);
} // 这里可以继续添加其他基本类型的处理
else {
// 对于引用类型
mv.visitVarInsn(Opcodes.ALOAD, index);
}
// 这里可以添加对参数的处理代码,例如设置字段值等
mv.visitFieldInsn(Opcodes.PUTFIELD, Util.packageToPath(clazz), arg.getKey(), Type.getDescriptor(argType));
index++;
}
// 正常结束方法
mv.visitInsn(Opcodes.RETURN);
// 计算操作数栈和局部变量表的大小
mv.visitMaxs(1, 1); // 这里需要根据实际情况调整
});
}
private void field(int accessFlags, Class<?> type, String field) {
CW.visitField(accessFlags, field, Type.getDescriptor(type), null, null);
}
public void annotation(Class<?> type,Consumer<AnnotationVisitor> body){
AnnotationVisitor av = CW.visitAnnotation(Type.getDescriptor(Generated.class), true);
body.accept(av);
av.visitEnd();
}
public void defineMethod(int accessFlags, String method, String descriptor, Consumer<MethodVisitor> body) {
MethodVisitor getterMV = CW.visitMethod(accessFlags, method, descriptor, null, null);
getterMV.visitCode();
body.accept(getterMV);
getterMV.visitEnd();
}
public void setter(String clazz, Class<?> type, String field) {
String setterName = String.format("set%s", Util.getMethodName(field));
String setterDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(type));
this.defineMethod(Opcodes.ACC_PUBLIC, setterName, setterDescriptor, mv -> {
mv.visitVarInsn(Opcodes.ALOAD, 0); // 加载当前类的引用(对于静态方法,这里可以省略)
mv.visitVarInsn(Opcodes.ALOAD, 1); // 加载参数中的String引用
mv.visitFieldInsn(Opcodes.PUTFIELD, Util.packageToPath(clazz), field, Type.getDescriptor(type));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2); // 设置操作数栈和局部变量表的最大大小
});
}
public void getter(String clazz, Class<?> type, String field) {
String getterName = String.format("get%s", Util.getMethodName(field));
String getterDescriptor = Type.getMethodDescriptor(Type.getType(type));
this.defineMethod(Opcodes.ACC_PUBLIC, getterName, getterDescriptor, mv -> {
mv.visitVarInsn(Opcodes.ALOAD, 0); // 加载当前类的引用(对于静态方法,这里可以省略)
mv.visitFieldInsn(Opcodes.GETFIELD, Util.packageToPath(clazz), field, Type.getDescriptor(type));
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(1, 1); // 设置操作数栈和局部变量表的最大大小
});
}
public <T, R> void apply(Class<T> in, Class<R> out) {
String setterDescriptor = Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class));
Method[] gets = Arrays.stream(in.getMethods()).filter(mv -> mv.getName().startsWith("get")).toArray(Method[]::new);
Method[] sets = Arrays.stream(out.getMethods()).filter(mv -> mv.getName().startsWith("set")).toArray(Method[]::new);
this.defineMethod(Opcodes.ACC_PUBLIC, "apply", setterDescriptor, mv -> {
mv.visitTypeInsn(Opcodes.NEW, Util.packageToPath(out.getTypeName())); // 创建一个新的 B 对象
mv.visitInsn(DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Util.packageToPath(out.getTypeName()), "<init>", "()V", false); // 调用 B 对象的构造方法
mv.visitVarInsn(ASTORE, 2);
for (int i = 0; i < Math.min(gets.length, sets.length); i++) {
Method get = gets[i];
Method set = sets[i];
//仅当getter返回类型与setter参数类型相等时
if (get.getReturnType().equals(Arrays.stream(set.getParameterTypes()).findFirst().orElse(null))) {
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, Util.packageToPath(in.getTypeName()));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Util.packageToPath(in.getTypeName()), gets[0].getName(), Type.getMethodDescriptor(get), false); // 调用 A 对象的 getProperty 方法
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Util.packageToPath(out.getTypeName()), sets[0].getName(), Type.getMethodDescriptor(set), false); // 调用 B 对象的 setProperty 方法
}
}
mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitInsn(ARETURN); // 返回B对象
mv.visitMaxs(-1, -1);
});
}
public byte[] getBytes() {
return CW.toByteArray();
}
public void toFile(String location, String clazz) {
Path path = Paths.get(location, String.format("%s.class", Util.packageToPath(clazz)));
File file = path.toFile();
if (file.exists()) {
boolean _ = file.delete();
}
try (OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
out.write(CW.toByteArray());
} catch (IOException e) {
Logger.getGlobal().info(e.getMessage());
}
}
}
ASM字节码技术介绍
https://blog.cikaros.top/doc/833aa7fb.html