asm 包是asm框架的core包。
这篇介绍怎么用asm创建和修改编译后的class。
asm创建和改变Java字节码是通过ClassVisitor抽象类,
classVistor像一个过滤器,它通过委托调用传递给它的classVistor实例。
classreader用来解析一个已存在的类。
下面是模仿javap打印一个类的信息,这里classReader承担生产者的角色,ClassPrinter作为消费者。
package com.liu.asm;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(Opcodes.ASM6);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println(name+" extend "+superName +" implements "+interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
System.out.println(" "+descriptor+" "+name);
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println(" "+name+descriptor);
return null;
}
public static void main(String[] args) throws IOException {
ClassPrinter classPrinter=new ClassPrinter();
InputStream cl=ClassLoader.getSystemResourceAsStream(Student.class.getName().replace(".","/")+".class");
ClassReader classReader=new ClassReader(cl);
classReader.accept(classPrinter,0);
}
}
输出结果:
com/liu/asm/Student extend java/lang/Object implements [Ljava.lang.String;@4dc63996
Ljava/lang/String; name
I age
()V
getAge()I
产生一个新类,只需要用ClassWriter就可以。
创建字节码:
public class CW1 {
public static void main(String[] args) throws IOException {
ClassWriter cw=new ClassWriter(0);
//类版本,访问标志以及修饰符,类全名,泛型,父类,接口
cw.visit(V1_7,ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE,
"org/by/Cwtest",null,"java/lang/Object",
new String[]{"org/by/ICw"});
//访问标志,名字,类型,泛型,值
cw.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"LESS","I",
null,new Integer(-1)).visitEnd();
cw.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"EQUAL","I",
null,new Integer(0)).visitEnd();
cw.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"GRATER","I",
null,new Integer(1)).visitEnd();
//访问标志,名字,签名,泛型,throws异常
cw.visitMethod(ACC_PUBLIC+ACC_ABSTRACT,"compareTo","(Ljava/lang/Object;)I",
null,null).visitEnd();
cw.visitEnd();//通知classWriter,类定义完成了
String systemRootUrl = (new File("")).toURI().toURL().getPath();
File file=new File(systemRootUrl+"org/by/Cwtest.class");
String parent=file.getParent();
File parent1=new File(parent);
parent1.mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(cw.toByteArray());
}
}
等价于
package org.by;
public interface Cwtest extends ICw {
int LESS = -1;
int EQUAL = 0;
int GRATER = 1;
int compareTo(Object var1);
}
对应二进制文件
下面是使用classreader解析类,通过classwriter修改。在二者之间加上适配器。
覆盖一个类的visit方法,可以修改类的版本信息,名字,接口信息,以及父类等。当然,改类名是一个比较复杂的事情,因为很多地方都可能引用这个名字,你需要修改所有的地方。
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
上面的转变看起来不大高效,因为只改变了4个字节,却解析了整个原始类。更高效的做法是复制未修改的部分,只修改变化的部分。因此,ASM框架自动对方法做了一些优化:
这些优化主要用于增加字段或方法以及命令。对于修改以及减少的变化,就作用不大了。
修改一个类,直接用类加载器加载的话,只会在这个类加载器中生效,如果想在所有类加载器中生效,需要使用ClassFileTransformer(这个类定义在java.lang.instrument)。这一篇描述了ClassFileTransformer使用相关
移除类成员
下面的示例,移除了类Person中do开头的方法,并修改了类名Person为Cwtest。
如果想移除某个方法,只需要返回Null(也就是不返回method)。
public class RemoveMethod extends ClassVisitor{
public RemoveMethod(ClassWriter cw) {
super(Opcodes.ASM6,cw);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, "org/by/Cwtest", signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.startsWith("do")){
return null;
}
return cv.visitMethod(access, name, descriptor, signature, exceptions);
}
public static void main(String[] args) throws IOException {
ClassWriter classWriter=new ClassWriter(3);
RemoveMethod removeMethod=new RemoveMethod(classWriter);
ClassReader classReader=new ClassReader("com.liu.asm.Student");
classReader.accept(removeMethod,0);
File file=new File("org/by/Cwtest.class");
String parent=file.getParent();
File parent1=new File(parent);
parent1.mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(classWriter.toByteArray());
}
}
源类:
public class Student implements Person
{
public static String name;
private int age;
public int getAge() {
return age;
}
void do1(){
System.out.println("wqwe");
}
void say(){
System.out.println("2231");
}
}
结果:
public class Cwtest implements Person {
public static String name;
private int age;
public Cwtest() {
}
public int getAge() {
return super.age;
}
void say() {
System.out.println("2231");
}
}
添加类成员,比如添加一个字段。为了确保字段名不重复,添加字段的操作,在访问了所有的字段信息之后执行。
public class AddFiled extends ClassVisitor {
String filedName="thisIsNewAddF1";
private int acc=Opcodes.ACC_PUBLIC;
boolean isPresent=false;
public AddFiled( ClassVisitor classVisitor) {
super(Opcodes.ASM6, classVisitor);
}
@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(acc,filedName,"I",null,3);
if (fv!=null){
fv.visitEnd();
}
}
super.visitEnd();
}
public static void main(String[] args) throws IOException {
ClassWriter classWriter=new ClassWriter(3);
AddFiled addFiled=new AddFiled(classWriter);
ClassReader classReader=new ClassReader("com.liu.asm.Student");
classReader.accept(addFiled,0);
File file=new File("org/by/Cwtest.class");
String parent=file.getParent();
File parent1=new File(parent);
parent1.mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream=new FileOutputStream(file);
fileOutputStream.write(classWriter.toByteArray());
}
}
生成的结果类的字节码信息如下:
Last modified 2018-10-24; size 791 bytes
MD5 checksum c480f9e6efb6a42b829155ac9fb15362
Compiled from "Student.java"
public class com.liu.asm.Student implements com.liu.asm.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/liu/asm/Student
#2 = Class #1 // com/liu/asm/Student
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 com/liu/asm/Person
#6 = Class #5 // com/liu/asm/Person
#7 = Utf8 Student.java
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 age
#11 = Utf8 I
#12 = Utf8
#13 = Utf8 ()V
#14 = NameAndType #12:#13 // "":()V
#15 = Methodref #4.#14 // java/lang/Object."":()V
#16 = Utf8 this
#17 = Utf8 Lcom/liu/asm/Student;
#18 = Utf8 getAge
#19 = Utf8 ()I
#20 = NameAndType #10:#11 // age:I
#21 = Fieldref #2.#20 // com/liu/asm/Student.age:I
#22 = Utf8 do1
#23 = Utf8 java/lang/System
#24 = Class #23 // java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#28 = Fieldref #24.#27 // java/lang/System.out:Ljava/io/PrintStream;
#29 = Utf8 wqwe
#30 = String #29 // wqwe
#31 = Utf8 java/io/PrintStream
#32 = Class #31 // java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
#35 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#36 = Methodref #32.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V
#37 = Utf8 say
#38 = Utf8 2231
#39 = String #38 // 2231
#40 = Utf8 thisIsNewAddF1
#41 = Integer 3
#42 = Utf8 ConstantValue
#43 = Utf8 Code
#44 = Utf8 LineNumberTable
#45 = Utf8 LocalVariableTable
#46 = Utf8 SourceFile
{
public static java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
public int thisIsNewAddF1;
descriptor: I
flags: ACC_PUBLIC
ConstantValue: int 3
public com.liu.asm.Student();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #15 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/liu/asm/Student;
public int getAge();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #21 // Field age:I
4: ireturn
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/liu/asm/Student;
void do1();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: getstatic #28 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #30 // String wqwe
5: invokevirtual #36 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/liu/asm/Student;
void say();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: getstatic #28 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #39 // String 2231
5: invokevirtual #36 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 19: 0
line 20: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/liu/asm/Student;
}
SourceFile: "Student.java"
Type 工具类
Type.getType(String.class).getInternalName()获取一个类的类名,只对类和接口有效。java/lang/String
Type.getType(String.class).getDescriptor()获得一个类型的 描述符。Ljava/lang/String;
Type.INT_TYPE.getDescriptor() 获取基本类型的描述符
TraceClassVisitor用来查看生成的字节码对应的类是否是想要的类。
public static void main(String[] args) throws IOException {
ClassWriter cw=new ClassWriter(3);
TraceClassVisitor tv=new TraceClassVisitor(cw,new PrintWriter(System.out));
tv.visit(V1_8,ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE,
"org/by/Cwtest",null,"java/lang/Object",
null);
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"LESS","I",
null,new Integer(-1)).visitEnd();
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"EQUAL","I",
null,new Integer(0)).visitEnd();
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"GRATER","I",
null,new Integer(1)).visitEnd();
tv.visitMethod(ACC_PUBLIC+ACC_ABSTRACT,"compareTo","(Ljava/lang/Object;)I",
null,null).visitEnd();
tv.visitEnd();
}
打印结果:
// class version 52.0 (52)
// access flags 0x601
public abstract interface org/by/Cwtest {
// access flags 0x19
public final static I LESS = -1
// access flags 0x19
public final static I EQUAL = 0
// access flags 0x19
public final static I GRATER = 1
// access flags 0x401
public abstract compareTo(Ljava/lang/Object;)I
}
CheckClassAdapter这类是用来检查它的方法调用以及参数是否正确。
public static void main(String[] args) throws IOException {
ClassWriter cw=new ClassWriter(3);
CheckClassAdapter cca = new CheckClassAdapter(cw);
TraceClassVisitor tv=new TraceClassVisitor(cca,new PrintWriter(System.out));
tv.visit(V1_8,ACC_PUBLIC+ACC_ABSTRACT+ACC_INTERFACE,
"org/by/Cwtest",null,"java/lang/Object",
null);
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"LESS","I",
null,new Integer(-1)).visitEnd();
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"EQUAL","I",
null,new Integer(0)).visitEnd();
tv.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"GRATER","I",
null,new Integer(1)).visitEnd();
tv.visitMethod(ACC_PUBLIC+ACC_ABSTRACT,"compareTo","(Ljava/lang/Object;)I",
null,null).visitEnd();
tv.visitEnd();
}
ASMifier用来产生asm代码,当有一个类时,使用这个类读取目标类,会产生产生这个字节码所需要的asm代码。如果不知道怎么写ASM代码,可以先写Java源码,然后用这个类产生相应的代码。
调用方式:
ASMifier.main(new String[]{"com.liu.hash.DataItem"});
Opcodes可以分为2类:一类把局部变量表的值压栈,另一类只作用于操作数栈,计算,出栈入栈。
修改方法必须执行的顺序
visitAnnotationDefault?
( visitAnnotation | visitParameterAnnotation | visitAttribute )*
( visitCode
( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |
visitLocalVariable | visitLineNumber )*
visitMaxs )?
visitEnd
public class AddTimerAdapter extends ClassVisitor {
private String owner;
private boolean isInterface;
public AddTimerAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & ACC_INTERFACE) != 0;
}
@Override public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
if (!isInterface && mv != null && !name.equals("")) {
mv = new AddTimerMethodAdapter(mv);
}
return mv;
}
@Override public void visitEnd() {
if (!isInterface) {
FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, "timer",
"J", null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
class AddTimerMethodAdapter extends MethodVisitor {
public AddTimerMethodAdapter(MethodVisitor mv) {
super(ASM4, mv);
}
@Override public void visitCode() {
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LSUB);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
mv.visitInsn(opcode);
}
@Override public void visitMaxs(int maxStack, int maxLocals) {
mv.visitMaxs(maxStack + 4, maxLocals);
}
}
}
LocalVariablesSorter 可以获取局部变量的索引