ASM是什么:ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类。 ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。 ASM提供与其他Java字节码框架类似的功能,但专注于性能。 因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。
1、如何用ASM读出整个类:
利用ASM的ClassReader将class文件或Stream流读进ClassReader中。
ClassReader reader = new ClassReader(ClassPrinter.class.getClassLoader().getResourceAsStream("com/jzh/designpattern/proxy/Movable.class"));
ClassReader reader = new ClassReader("com.jzh.designpattern.proxy.Movable");
2、如何对ClassReader读出的类进行操作:
利用ClassVisitor,当传入某个类时,这个类会通过访问者模式遍历ClassVisitor的每一个节点,ClassVisitork节点大致有以下几个:
方法名称 | 描述 | 节点 |
visit | 访问类信息 | ClassVisitor |
visitField | 访问类成员变量或类变量信息 | FieldVisitor |
visitMethod | 访问类中的方法 | MethodVisitor |
visitAnnotation | 访问类的注释信息 | AnnotationVisitor |
可以通过继承ClassVisitor重写ClassVisitor的方法从而对类进行操作。
public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(ASM4);
}
public ClassPrinter(ClassWriter classWriter) {
super(ASM4, classWriter);
}
/**
* 访问类
* @param version jdk版本
* @param access 访问修饰符
* @param name 类名
* @param signature 泛型信息
* @param superName 父类类名
* @param interfaces 实现的接口
*/
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.print(name + " ");
if (signature != null) {
System.out.print("<" + signature + "> ");
}
if (signature != null) {
System.out.print("extends " + superName + " ");
}
if (interfaces != null && interfaces.length > 0) {
System.out.print("implements " + interfaces[0]);
for (int i = 1; i < interfaces.length; i++) {
System.out.print(", " + interfaces[i]);
}
}
System.out.println(" {");
super.visit(version, access, name, signature, superName, interfaces);
}
/**
* 访问类成员变量
* @param access 访问修饰符
* @param name 方法名
* @param desc 返回类型
* @param signature 泛型类型
* @param value 初始值(final时才会打印)
* @return
*/
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println(" " + desc + " " + name + " ");
return super.visitField(access, name, desc, signature, value);
}
/**
* 访问类方法
* @param access 访问修饰符
* @param name 方法名称
* @param desc 方法返回类型
* @param signature 泛型
* @param exceptions 抛出的异常
* @return
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(" " + desc + " " + name + "();");
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
System.out.println("}");
super.visitEnd();
}
}
如何向ClassVisitor传入类:
ClassPrinter classPrinter = new ClassPrinter();
ClassReader reader = new ClassReader("java.lang.Runnable");
reader.accept(classPrinter, 0);
运行结果:
java/lang/Runnable {
()V run();
}
Process finished with exit code 0
3、MethodVisitor
MethodVisitor节点内部也是一个visitor,可以继承MethodVisitor对方法进行操作,例如:
在调用名称为move的方法前,静态调用LogProxy的before方法,在调用名称为move2的方法前,静态调用LogProxy的before2方法
ClassVisitor classVisitor = new ClassVisitor(ASM4, classWriter) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals("move")) {
return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitCode() {
super.visitMethodInsn(INVOKESTATIC, "com/jzh/designpattern/proxy/asm/LogProxy", "before", "()V", false);
super.visitCode();
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
super.visitFieldInsn(opcode, owner, name, descriptor);
}
@Override
public void visitEnd() {
super.visitEnd();
}
};
} else if (name.equals("move2")) {
return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitCode() {
super.visitMethodInsn(INVOKESTATIC, "com/jzh/designpattern/proxy/asm/LogProxy", "before2", "()V", false);
super.visitCode();
}
};
}
return new MethodVisitor(ASM4, super.visitMethod(access, name, descriptor, signature, exceptions)) {};
}
};
附:官方文档解释:
The visitCode and visitMaxs methods can therefore be used to detect
the start and end of the method’s bytecode in a sequence of events.
Like for classes, the visitEnd method must be called last, and is used
to detect the end of a method in a sequence of events
执行过程:
较重要方法解释:
MethodVisitor常用方法名 | 描述 |
visitCode | ASM开始扫描方法 |
visitMaxs | 用以确定类方法在执行时候的堆栈大小 |
visitEnd | 表示方法输出完毕 |
4、如何将类写入内存:
①用ClassWriter写一个类(各参数常量储存在org.objectweb.asm.Opcodes中)
ClassWriter cw = new ClassWriter(0);
// 生成类或者接口
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE, "pkg/ASMWriteClassTest",
null, "java/lang/Object", null);
// 生成成员变量
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "testField", "I", null, 10).visitEnd();
// 生成方法
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "testMethod", "(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
②定义一个classLoader继承自ClassLoader,并将写好的类加载到内存中
public class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] bytes) {
return defineClass(name, bytes, 0, bytes.length);
}
}
byte[] b = cw.toByteArray();
MyClassLoader myClassLoader = new MyClassLoader();
Class c = myClassLoader.defineClass("pkg.ASMWriteClassTest", b);
5、如何用ASM实现动态代理:
①利用ClassReader将目标类读入到ClassReader中
ClassReader reader = new ClassReader(ASMProxyTest.class.getClassLoader().getResourceAsStream("com/jzh/designpattern/proxy/Car.class"));
②将ClassWriter放入到ClassVisitor中
ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassPrinter(classWriter);
③用ClassVisitor对ClassReader进行访问,使得将修改后的目标类加载到了ClassWriter中
reader.accept(classVisitor, 0);
④用ClassLoader加载目标类,并将ClassLoader中的目标类重新定义得到新的Class对象
byte[] bytes = classWriter.toByteArray();
MyClassLoader classLoader = new MyClassLoader();
classLoader.loadClass("com.jzh.designpattern.proxy.Car");
Class c2 = classLoader.defineClass("com.jzh.designpattern.proxy.Car", bytes);
⑤利用反射获取类的实例
Movable moveable = null;
try {
moveable = (Movable) c2.getDeclaredConstructor().newInstance(null);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
moveable.move();
moveable.move2();
打印结果:
proxy start...
car moving 1...
proxy start2...
car moving 2...