ASM框架使用(二)--类的简单变动以及工具类的使用

asm 包是asm框架的core包。
这篇介绍怎么用asm创建和修改编译后的class。

Java源码中类型,在字节码中是用以下类型代表的:
这里写图片描述

方法描述(方法名不在方法描述中,在常量池中):
这里写图片描述

asm创建和改变Java字节码是通过ClassVisitor抽象类,
这里写图片描述

class vistor必须按照以下执行顺序:
这里写图片描述

	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
 <init>()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框架自动对方法做了一些优化:

  • 如果ClassReader发现accept接收的参数ClassVisitor返回一个MethodVisitor,那么这意味着这个方法的内容不会被改变,甚至不会被应用感知
  • 这种情况下,classReader不会解析这个方法内容,不会产生相应事件,只会复制代表这个方法的字节数组

这些优化主要用于增加字段或方法以及命令。对于修改以及减少的变化,就作用不大了。

修改一个类,直接用类加载器加载的话,只会在这个类加载器中生效,如果想在所有类加载器中生效,需要使用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               <init>
  #13 = Utf8               ()V
  #14 = NameAndType        #12:#13        // "<init>":()V
  #15 = Methodref          #4.#14         // java/lang/Object."<init>":()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."<init>":()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("<init>")) {
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 可以获取局部变量的索引

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值