ASM是什么
ASM是一个致力于字节码和分析的框架,它用来动态生成类或者增强既有类的功能。ASM可以直接创建class文件,也可以在类被加载到jvm之前动态的改变类的行为。class文件是按照严格格式存储的,这些class文件拥有足够的元数据来解析类中所有元素:类名称、方法、属性以及Java字节码指令,ASM能够直接从class文件中读取信息获得类信息,同时也能根据需求直接通过字节码修改class文件。虽然,ASM提供了其他字节码工具相同的功能,但是它更关注执行效率,它被设计的更小更快。其实,jdk内部已经使用了ASM,同学们可以看一下jdk.interbal.org.objectweb.asm包,在实现lambda表达式调用, Nashorn编译器时都有用到。
ASM基础
Opcodes接口定义了一些常量,如版本号,访问标识符,字节码等信息
ClassReader用于读取class文件,主要用于class文件分析,可接受一个ClassVisitor;ClassReader会将解析过程中产生的类的部分信息,比如访问标识符,字段,方法存在ClassVisitor中,后者在接收到对应信息后,进行各自的处理。
ClassWriter是ClassVisitor的子类,负责calss文件的输出和生成。ClassVisitor在进行字段和方法处理的时候,会委托给FieldVisitor和MethodVisitor进行处理。FieldWriter和MethodWriter是FieldVisitor和MethodVisitor的子类;当ClassWriter依赖这两个类进行字段和方法的处理。
ClassWriter的参数:
0 :你需要手动计算,最大操作数栈,局部变量表,桢变化
COMPUTE_MAXS:自动计算局部变量表和操作数栈,但是必须要调用visitMaxs,方法参数会被忽略。桢变化需要手动计算
COMPUTE_FRAMES:全自动计算,但是必须要调用visitMaxs,方法参数会被忽略。
但是有时间成本,COMPUTE_MAXS比0慢10%,COMPUTE_FRAMES慢一倍。
下面我们介绍一下ClassVisitor,还是常用方式看源码
/**
* 访问一个类,这个类的方法必须按照下面的顺序调用:
* A visitor to visit a Java class. The methods of this class must be called in the following order:
* {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code
* visitPermittedSubtype} ][ {@code visitOuterClass} ] ( {@code visitAnnotation} | {@code
* visitTypeAnnotation} | {@code visitAttribute} )* ( {@code visitNestMember} | {@code
* visitInnerClass} | {@code visitField} | {@code visitMethod} )* {@code visitEnd}.
*
* @author Eric Bruneton
*/
public abstract class ClassVisitor {
通过上面的注释我们知道访问是有顺序的。
ClassVisitor,FieldVisitor,MethodVisitor(使用这些类前阅读以下源码的注释,visitXX方法的调用,都是有顺序的)都可以使用委托的方式,将实际工作交给内部委托类进行的。
下面我们介绍descriptor:主要是对方法参数和返回值进行描述
- 字节码指令中的数据类型:
Java Type | Type descriptor |
---|---|
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 |
- 字段和方法中描述符或签名的数据类型:
Method declaration in source file | Method descriptor |
---|---|
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; |
下面我们介绍signature:泛型中才会将该属性编译进字节码文件,除了方法参数和返回值,还包含了泛型信息
举个例子:
public class Test<E> {
private E e;
public Test(E e) {
this.e = e;
}
/**
*descriptor:(Ljava/lang/Object;)V
*signature: <E:Ljava/lang/Object;>(TE;)V
*/
public <E> void sysE(E e) {
...
}
/**
*descriptor:(Ljava/lang/Object;)V
*signature: (TE;)V
*/
public void sys(E e) {
...
}
}
T表示参数是泛型类型,E表示其签名,V表示返回值。
了解了上面的基础知识后,我们开始进行实地操作。
ASM使用举例:
1.ClassWriter生成字节码
public class ASMTest {
public static void main(String[] args) throws IOException {
// 生成一个类只需要ClassWriter组件即可
ClassWriter cw = new ClassWriter(0);
//通过visit方法确定类的头部信息
cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT + Opcodes.ACC_INTERFACE,
"com/pch/asm/TestASM", null, "java/lang/Object", null);
//定义类的属性
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"A", "I", null, 1).visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"B", "Ljava/lang/String;", null, "test").visitEnd();
cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"C", "I", null, 3).visitEnd();
//定义类的方法
cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "test",
"(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd(); //使cw类已经完成
//将cw转换成字节数组写到文件里面去
byte[] data = cw.toByteArray();
File file = new File("D://TestASM.class");
FileOutputStream out = new FileOutputStream(file);
out.write(data);
out.close();
}
}
通过IDEA打开生成的class文件,内容如下:
package com.pch.asm;
public interface TestASM {
int A = 1;
String B = "test";
int C = 3;
int test(Object var1);
}
2.修改一个存在类(修改一个类,直接用类加载器加载的话,只会在这个类加载器中生效)
移除类成员
如果想移除某个方法,只需要返回Null(也就是不返回method)
import org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;
public class RemoveMethod extends ClassVisitor {
public RemoveMethod(ClassWriter cw) {
super(Opcodes.ASM8,cw);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, "com/pch/asm/Remove", signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.startsWith("print")){
return null;
}
return cv.visitMethod(access, name, descriptor, signature, exceptions);
}
public static void main(String[] args) throws IOException {
ClassWriter classWriter=new ClassWriter(0);
RemoveMethod removeMethod=new RemoveMethod(classWriter);
ClassReader classReader=new ClassReader("com.pch.asm.RemoveTest");
classReader.accept(removeMethod,0);
FileOutputStream fileOutputStream=new FileOutputStream("D://Remove.class");
fileOutputStream.write(classWriter.toByteArray());
}
}
操作类
package com.pch.asm;
public class RemoveTest {
public static String name;
private int age;
public int getAge() {
return age;
}
public static String getName() {
return name;
}
void print() {
System.out.println(age);
}
}
移除后的class文件内容如下
package com.pch.asm;
public class Remove {
public static String name;
private int age;
public Remove() {
}
public int getAge() {
return this.age;
}
public static String getName() {
return name;
}
}
添加类成员
比如添加一个字段。为了确保字段名不重复,添加字段的操作,在访问了所有的字段信息之后执行。
import org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;
public class AddField extends ClassVisitor {
private String filedName = "newAdd";
private boolean isPresent = false;
public AddField(ClassWriter cw) {
super(Opcodes.ASM8,cw);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(filedName)){
isPresent = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public void visitEnd() {
if (!isPresent){
//没有这个字段
FieldVisitor fv= this.cv.visitField(Opcodes.ACC_PUBLIC, filedName,"I",null,3);
if (fv!=null){
fv.visitEnd();
}
}
super.visitEnd();
}
public static void main(String[] args) throws IOException {
ClassWriter classWriter=new ClassWriter(0);
AddField addField=new AddField(classWriter);
ClassReader classReader=new ClassReader("com.pch.asm.Remove");
classReader.accept(addField,0);
FileOutputStream fileOutputStream=new FileOutputStream("D://AddField.class");
fileOutputStream.write(classWriter.toByteArray());
}
}
添加属性后的class文件内容如下
package com.pch.asm;
public class AddTest {
public static String name;
private int age;
public int newAdd;
public AddTest() {
}
public int getAge() {
return this.age;
}
public static String getName() {
return name;
}
void print() {
System.out.println(this.age);
}
}
改变修改方法
例如:增加方法的执行耗时
public class DEMO {
public static void changeClassMethod() {
try {
ClassReader reader = new ClassReader("com.pch.asm.TestTime");
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ClassVisitor change = new ChangeVisitor(writer);
reader.accept(change, ClassReader.EXPAND_FRAMES);
try {
FileOutputStream fos = new FileOutputStream("D:/TestTime.class");
fos.write(writer.toByteArray());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 方法变化visitor
*/
private static class ChangeVisitor extends ClassVisitor {
ChangeVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM8, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("<init>")) {
return methodVisitor;
}
return new ChangeAdapter(Opcodes.ASM8, methodVisitor, access, name, desc);
}
}
/**
* 使用AdviceAdapter要引入asm-commons
* 方法体具体变化的操作都在这里操作
*/
private static class ChangeAdapter extends AdviceAdapter {
/** 参数的index */
private int startTimeId = -1;
/** 方法名 */
private String methodName;
ChangeAdapter(int api, MethodVisitor mv, int access, String name, String desc) {
super(api, mv, access, name, desc);
methodName = name;
}
/**
* 进入方法体时调用
*/
@Override
protected void onMethodEnter() {
super.onMethodEnter();
startTimeId = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitIntInsn(LSTORE, startTimeId);
}
/**
* 退出方法体调用
* @param opcode opcode
*/
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
int durationId = newLocal(Type.LONG_TYPE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, startTimeId);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, durationId);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("The cost time of " + methodName + "() is ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, durationId);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(" ms!");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
}
}
通过上面的介绍我们已经对ASM有了一定了解。
ASM相关我们先介绍到这,有说的不准确的地方欢迎指正,下一篇文章我们介绍另一个字节码框架javassist。