参考《深入理解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)); } }