参考《深入理解JVM字节码》、《ASM4 中文手册》
从书中 demo 到按自己的想法引出共写了 4 个 demo。
首先创建被操作的类,DemoClass,结构非常简单。
// DemoClass
public class DemoClass {
public int a;
public int b;
}
Demo1:ASM 访问 Field 和 Method
public class TestVisit {
public static void main(String[] args) throws IOException {
ClassReader cr = new ClassReader(DemoClass.class.getName());
/**
* 0 手动计算
* COMPUTE_MAXS 自动计算 帧、操作数栈大小和局部变量表大小
* COMPUTE_FRAMES 类似2,不再调用 visitFrame
*/
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
// 通过重写 visitField 和 visitMethod 可以做类似切面的操作
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
System.out.println("field: " + name);
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("method: " + name);
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
}
}
Demo2:ASM 添加 Field
/**
* 不创建新的字节码文件,直接读取 bytes 创建 Class 对象
*/
public class TestVisit2 extends ClassLoader {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassReader cr = new ClassReader(DemoClass.class.getName());
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public void visitEnd() {
super.visitEnd();
// 通过 visitField() 添加 Field
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "xyz",
"Ljava/lang/String;", null, null);
if (fv != null) fv.visitEnd();
}
};
cr.accept(cv, 0);
// 转换成 bytes
byte[] bytes = cw.toByteArray();
// 通过 ClassLoader.defineClass() 从 bytes 生成 Class 对象
Class<?> clazz = new TestVisit2().defineClass("DemoClass", bytes, 0, bytes.length);
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
}
}
Demo3:ASM 添加 Field (生成新的类)
对旧类进行修改后,创建新的字节码文件,这里要求新的 .class 文件名的同时也要改变类名(相当于变成一个新的类)。
/**
* 创建一个新的增强类
*/
public class TestVisit3 extends ClassLoader {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ClassReader cr = new ClassReader(DemoClass.class.getName());
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
// 在 visit 的 name 部分进行修改,更改生成的类名
super.visit(version, access, name+"Enhancer", signature, superName, interfaces);
}
@Override
public void visitEnd() {
super.visitEnd();
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "xyz",
"Ljava/lang/String;", null, null);
if (fv != null) fv.visitEnd();
}
};
cr.accept(cv, 0);
byte[] bytes = cw.toByteArray();
// 通过 FileUtils 存储字节码文件
FileUtils.writeByteArrayToFile(new File(TestVisit3.class
.getClassLoader()
.getResource("")
.getPath() + "DemoClassEnhancer.class"), bytes);
// 可以拿到字节码文件,正常加载。相当于创建了一个新的类。
Class<?> clazz = Class.forName("DemoClassEnhancer");
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
}
}
Demo4:ASM 添加 Filed 并修改 <init>
然后我尝试在Demo3的情况下,再给 DemoClass 初始化赋值,如下:
public class DemoClass {
public int a = 0;
public int b = 1;
}
会发现开始报错了,查看 DemoClassEnhancer:
public class DemoClassEnhancer {
public int a;
public int b;
public String xyz;
public DemoClassEnhancer() {
super.a = 0;
super.b = 1;
}
}
很明显,这个 super.a、super.b 并不是想要的。一个简单的办法是,重写 DemoClassEnhancer 的 <init> 部分。
这里我复制了上面的 DemoClass,修改其名字为 DemoClass2。
/**
* DemoClass2 中添加初始化值后,就会报错
*/
public class TestVisit4 extends ClassLoader {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ClassReader cr = new ClassReader(DemoClass2.class.getName());
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name+"Enhancer", signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// 去掉之前的 <init> 方法,因为下面将添加一个新的
if ("<init>".equals(name)) {
return null;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
super.visitEnd();
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "xyz",
"Ljava/lang/String;", null, null);
if (fv != null) fv.visitEnd();
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(ICONST_0);
mv.visitFieldInsn(PUTFIELD, "DemoClass2Enhancer", "a", "I");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(ICONST_1);
mv.visitFieldInsn(PUTFIELD, "DemoClass2Enhancer", "b", "I");
mv.visitInsn(RETURN);
// COMPUTE_MAXS 和 COMPUTE_FRAMES 并不是指令,所以需要手动触发计算,值随便填
mv.visitMaxs(0,0);
mv.visitEnd();
}
};
cr.accept(cv, 0);
byte[] bytes = cw.toByteArray();
FileUtils.writeByteArrayToFile(new File(TestVisit4.class
.getClassLoader()
.getResource("")
.getPath() + "DemoClass2Enhancer.class"), bytes);
Class<?> clazz = Class.forName("DemoClass2Enhancer");
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
}
}