maven 导入asm的依赖 、 简单入门

asm项目结构

  • 1. 项目结构
    • 1.1. asm的仓库
    • 1.2 核心项目
      • 1.2.1 org.objectweb.asm 和 org.objectweb.asm.signature
      • 1.2.2 org.objectweb.asm.util
      • 1.2.3 org.objectweb.asm.commons
      • 1.2.4 org.objectweb.asm.tree
      • 1.2.5 org.objectweb.asm.tree.analysis
    • 1.3 其他项目
      • 1.3.1 asm-all和asm-parent
    • 1.4 maven依赖 asm的配置
  • 2. IDEA的插件
    • 2.1 ASM Bytecode outline 插件
    • 2.1 hexview插件
  • 3. asm的使用示例
    • 3.1 源码 & 字节码
    • 3.2 访问类的方法和字段
      • core API
      • tree API
    • 3.3 添加一个字段
      • core API
      • tree API
    • 3.4 新增方法
      • core API
      • tree API
    • 3.5 删除字段和方法
      • core API
      • tree API
    • 3.6 修改方法
      • core API
      • tree API
    • 3.7 AdviceAdapter的使用
      • core API
      • tree API
    • 3.8 try-catch
      • core API
  • 4. bytebuddy的示例

1. 项目结构

1.1. asm的仓库

maven仓库
maven 导入asm的依赖 、 简单入门_第1张图片

1.2 核心项目

1.2.1 org.objectweb.asm 和 org.objectweb.asm.signature

包定义了基于事件的API,并提供了类分析器和写入器组件。它们包含在 asm.jar 中。

1.2.2 org.objectweb.asm.util

包,位于asm-util.jar中,提供各种基于核心 API 的工具,可以在开发和调试 ASM 应用程序时使用。

1.2.3 org.objectweb.asm.commons

包提供了几个很有用的预定义类转换器,它们大多是基于核心 API 的。这个包包含在 asm-commons.jar中。

1.2.4 org.objectweb.asm.tree

包,位于asm-tree.jar 存档文件中,定义了基于对 象的 API,并提供了一些工具,用于在基于事件和基于对象的表示方法之间进行转换。

1.2.5 org.objectweb.asm.tree.analysis

包提供了一个类分析框架和几个预定义的 类 分析器,它们以树 API 为基础。这个包包含在 asm-analysis.jar 文件中。

1.3 其他项目

可以看出来1.2描述的几个包和1.1仓库是基本对应的。
但是除了asm-allasm-parent,与asm-debug等。

1.3.1 asm-all和asm-parent

asm-all包含了asm-parent,asm-parent 包含了所有的依赖。
一般来讲,使用者希望导入一个总的jar,其中包含子项目的jar。但是asm-all目前只停留在version 5.2,而最新子项目版本已经是8.1,不推荐使用。

  • asm-all 的pom
<project>
  <modelVersion>4.0.0modelVersion>

  <parent>
    <groupId>org.ow2.asmgroupId>
    <artifactId>asm-parentartifactId>
    <version>5.2version>
  parent>

  <name>ASM Allname>
  <groupId>org.ow2.asmgroupId>
  <artifactId>asm-allartifactId>
  <packaging>jarpackaging>
  
project>
  • asm-parent的pom
    parent包含了子项目的依赖

  <dependencyManagement>
    <dependencies>

      <dependency>
        <artifactId>asmartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

      <dependency>
        <artifactId>asm-treeartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

      <dependency>
        <artifactId>asm-analysisartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

      <dependency>
        <artifactId>asm-commonsartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

      <dependency>
        <artifactId>asm-utilartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

      <dependency>
        <artifactId>asm-xmlartifactId>
        <groupId>${project.groupId}groupId>
        <version>${project.version}version>
      dependency>

    dependencies>
  dependencyManagement>

      

1.4 maven依赖 asm的配置

并不推荐使用1.3导入全部,因为版本太老了
这里开发者需要这么引入。asm-commonsasm-util都包含了,基本的 core api ,tree api, 和analysis

   <properties>
        <asm.version>8.0.1asm.version>
    properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <artifactId>asmartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>

            <dependency>
                <artifactId>asm-treeartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>

            <dependency>
                <artifactId>asm-analysisartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>

            <dependency>
                <artifactId>asm-commonsartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>

            <dependency>
                <artifactId>asm-utilartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>

            <dependency>
                <artifactId>asm-xmlartifactId>
                <groupId>org.ow2.asmgroupId>
                <version>${asm.version}version>
            dependency>
        dependencies>
    dependencyManagement>
    
    <dependencies>
        <dependency>
            <artifactId>asm-commonsartifactId>
            <groupId>org.ow2.asmgroupId>
            <version>${asm.version}version>
        dependency>

        <dependency>
            <artifactId>asm-utilartifactId>
            <groupId>org.ow2.asmgroupId>
            <version>${asm.version}version>
        dependency>
    dependencies>

核心的包都具备了,是版本8.0.1
maven 导入asm的依赖 、 简单入门_第2张图片

2. IDEA的插件

装两个插件。
ASM Bytecode outlinehexview

2.1 ASM Bytecode outline 插件

  • 源码&字节码
    maven 导入asm的依赖 、 简单入门_第3张图片
  • 等价的ASM API
    maven 导入asm的依赖 、 简单入门_第4张图片

2.1 hexview插件

仅仅是以16进制显示.class文件
这个是真正的字节码,而asm插件中的字节码,是以人类可读的方式处理过。类似javap
maven 导入asm的依赖 、 简单入门_第5张图片

3. asm的使用示例

demo源码
这一步门槛比较高,需要对java 字节码的知识。和ASM 有全面的了解。

  • java 字节码知识推荐 《深入理解jvm 字节码》jvm 规范比对着看
  • ASM 推荐阅读官方指导手册,写的很容易懂。博客里提供了中英两版,比对者看。

《深入理解jvm 字节码》中的例子,使用基于事件core api基于文档模型tree API。如果具备字节码结构的知识、字节码指令知识、栈帧的模型的知识,去理解下面的例子非常容易,因为对类的加工,本质是增删改查字节码内容而已。

3.1 源码 & 字节码

源码

package com;

public class Application {
    public int a = 0;
    public int b = 1;

    public void test01() {

    }

    public void test02() {

    }
}

字节码
javap -verbose Application.class
编译后的类不包含packageimport信息。

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/Application.class
  Last modified 2020-6-30; size 464 bytes
  MD5 checksum 66668ae198d146acc35018549c757af7
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#20         // java/lang/Object."":()V
   #2 = Fieldref           #4.#21         // com/Application.a:I
   #3 = Fieldref           #4.#22         // com/Application.b:I
   #4 = Class              #23            // com/Application
   #5 = Class              #24            // java/lang/Object
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/Application;
  #16 = Utf8               test01
  #17 = Utf8               test02
  #18 = Utf8               SourceFile
  #19 = Utf8               Application.java
  #20 = NameAndType        #9:#10         // "":()V
  #21 = NameAndType        #6:#7          // a:I
  #22 = NameAndType        #8:#7          // b:I
  #23 = Utf8               com/Application
  #24 = Utf8               java/lang/Object
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #3                  // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

3.2 访问类的方法和字段

core API

接受编译过的Application.class的byte[]。ClassVisitor中覆盖FieldVisitorMethodVisitor方法,进行了方法和field的打印。
本类中ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG跳过对debug信息和方法Code属性的访问。

package com;

import org.objectweb.asm.*;

import static jdk.internal.org.objectweb.asm.Opcodes.ASM5;

/**
 * 访问类的方法和区域
 */
public class B_visitContent extends ClassLoader {

    public void visitByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        // 使用 new ClassWriter(0) 时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小
        ClassWriter cw = new ClassWriter(0);
        ClassVisitor cv = new ClassVisitor(ASM5, cw) {
            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                System.out.println("field in visitor: " + 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 in visitor: " + name);
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        };

        /*
            ClassReader.SKIP_DEBUG 跳过类文件中的调试信息,比如行号信息(LineNumberTable)
            ClassReader.SKIP_CODE 跳过方法体中的Code属性,比如(方法字节码、异常表等信息)
            ClassReader.SKIP_DEBUG 展开StackMapTable属性
            ClassReader.SKIP_DEBUG 跳过StackMapTable属性

         */
        cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
    }
    
    public static void main(String[] args) throws Exception{
        new B_visitContent().visitByCoreAPI();

    }

}

打印结果

field in visitor: a
field in visitor: b
method in visitor: 
method in visitor: test01
method in visitor: test02

tree API

等同于上面的例子

package com;


import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

import static jdk.internal.org.objectweb.asm.Opcodes.ASM5;

/**
 * 访问类的方法和区域
 */
public class B_visitContent extends ClassLoader {
    public void visitByTreeAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        // 使用 new ClassWriter(0) 时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小
        ClassNode cn = new ClassNode();
        /*
            ClassReader.SKIP_DEBUG 跳过类文件中的调试信息,比如行号信息(LineNumberTable)
            ClassReader.SKIP_CODE 跳过方法体中的Code属性,比如(方法字节码、异常表等信息)
            ClassReader.SKIP_DEBUG 展开StackMapTable属性
            ClassReader.SKIP_DEBUG 跳过StackMapTable属性
         */
        cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
        List<FieldNode> fields = cn.fields;

        for (int i = 0; i <fields.size() ; i++) {
            FieldNode fn = fields.get(i);
            System.out.println("field in visitor: " + fn.name);

        }

        List<MethodNode> methods = cn.methods;
        for (int i = 0; i <methods.size() ; i++) {
            MethodNode mn = methods.get(i);
            System.out.println("method in visitor: " + mn.name);
        }

        // 写,非必要
        ClassWriter cw = new ClassWriter(0);
        cr.accept(cw,0);
        byte[] bytesModified = cw.toByteArray();
    }

    public static void main(String[] args) throws Exception{
        new B_visitContent().visitByTreeAPI();
    }
}

打印

field in visitor: a
field in visitor: b
method in visitor: <init>
method in visitor: test01
method in visitor: test02

3.3 添加一个字段

Application.class添加一个public String name= "demo";字段。通过打印class文件,来展示修改

core API

核心的操作是visitField()+visitEnd()

package com;

import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.ASM5;

public class C_addField {
    public void addFieldByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(0);
        ClassVisitor cv = new ClassVisitor(ASM5, cw) {
            @Override
            public void visitEnd() {
                // visitEnd是访问类,访问的最后一个事件。在这个事件前添加变量就可以
                super.visitEnd();
                /**
                 * field的模板  public List list = null;
                 *
                 * Opcodes.ACC_PUBLIC   public 权限标志
                 * "name" 变量名
                 * "Ljava/lang/String" 变量返回值。字节码中,类型都是以L开头+类的全名(用/代替.)
                 * null 泛型的标识
                 * "demo" 值
                 */
                FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, "demo");
                if (fv != null) {
                    System.out.println("add");
                    fv.visitEnd();
                }
            }
        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv,0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(),Application.class,bytesModifield);

    }

    public void addFiledByTreeAPI() {

    }

    public static void main(String[] args) throws Exception {
        new C_addField().addFieldByCoreAPI();
    }
}

字节码
javap -verbose C_addField_Application.class
可以看到有name字段出现

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/C_addField_Application.class
  Last modified 2020-7-1; size 3150 bytes
  MD5 checksum 906f08c3db7beb47033fa2b8c176d9c7
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // "":()V
  #12 = Methodref          #4.#11         // java/lang/Object."":()V
  #13 = NameAndType        #6:#7          // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #8:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test01
  #20 = Utf8               test02
  #21 = Utf8               name
  #22 = Utf8               Ljava/lang/String;
  #23 = Utf8               demo
  #24 = String             #23            // demo
  #25 = Utf8               ConstantValue
  #26 = Utf8               Code
  #27 = Utf8               LineNumberTable
  #28 = Utf8               LocalVariableTable
  #29 = Utf8               SourceFile
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC
// 就是里
  public java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC
    ConstantValue: String demo

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

tree API

使用起来更简单,输出的字节码等同上面。

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;

import static org.objectweb.asm.Opcodes.ASM5;

public class C_addField {
    public void addFiledByTreeAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassNode cn = new ClassNode();
        cr.accept(cn,ASM5);
        // 新的field
        FieldNode fn = new FieldNode(Opcodes.ACC_PUBLIC,"name","Ljava/lang/String;",null,"demo");
        cn.fields.add(fn);

        //
        ClassWriter cw =new ClassWriter(0);
        cn.accept(cw);
        byte[] bytesModified = cw.toByteArray();
        Tool.save(this.getClass(),Application.class,bytesModified);
    }

    public static void main(String[] args) throws Exception {
        new C_addField().addFiledByTreeAPI();
    }
}

字节码
等同于core

3.4 新增方法

Application.java添加一个方法public void hello(int age, int name)

core API

核心的方法是visitMethod

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;

import static org.objectweb.asm.Opcodes.ASM5;

public class D_addMethod {
    public void addMethodByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(0);

        ClassVisitor cv = new ClassVisitor(ASM5, cw) {
            @Override
            public void visitEnd() {
                // visitEnd是访问类,访问的最后一个事件。在这个事件前添加变量就可以
                super.visitEnd();
                /**
                 * method的模板  public    List hello(int i );
                 *
                 * Opcodes.ACC_PUBLIC   public 权限标志
                 * "hello" 变量名
                 * "(ILjava/lang/String;)V" 方法类型, ()代表参数:I int  'Ljava/lang/String;' String ; V代表返回值void
                 * null 泛型的标识
                 * null 异常数组
                 */
                MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "hello", "(ILjava/lang/String;)V", null, null);
                if (mv != null) {
                    mv.visitEnd();
                }
            }
        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv, 0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(), Application.class, bytesModifield);

    }
    
    public static void main(String[] args) throws Exception {
        new D_addMethod().addMethodByCoreAPI();
    }
}

字节码
新增了hello方法

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/D_addMethod_Application.class
  Last modified 2020-7-1; size 505 bytes
  MD5 checksum 9ce9e7b87f84dd4a8df95f4e4407eea5
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // "":()V
  #12 = Methodref          #4.#11         // java/lang/Object."":()V
  #13 = NameAndType        #6:#7          // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #8:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test01
  #20 = Utf8               test02
  #21 = Utf8               hello
  #22 = Utf8               (ILjava/lang/String;)V
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               LocalVariableTable
  #26 = Utf8               SourceFile
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
  // hello
  public void hello(int, java.lang.String);
    descriptor: (ILjava/lang/String;)V
    flags: ACC_PUBLIC
}
SourceFile: "Application.java"

tree API

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import static org.objectweb.asm.Opcodes.ASM5;

public class D_addMethod {
    public void addMethodByTreeAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassNode cn = new ClassNode();
        cr.accept(cn, ASM5);
        // 新的field
        MethodNode fn = new MethodNode(Opcodes.ACC_PUBLIC, "hello", "(ILjava/lang/String;)V", null, null);
        cn.methods.add(fn);

        // 打印
        ClassWriter cw = new ClassWriter(0);
        cn.accept(cw);
        byte[] bytesModified = cw.toByteArray();
        Tool.save(this.getClass(), Application.class, bytesModified);
    }

    public static void main(String[] args) throws Exception {
        new D_addMethod().addMethodByCoreAPI();
    }

}

字节码
等同于core

3.5 删除字段和方法

core API

删除public int a = 0public void test01()。直接返回null即可。

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import static org.objectweb.asm.Opcodes.ASM5;

public class E_removeContent {
    public void removeContentByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(0);

        ClassVisitor cv = new ClassVisitor(ASM5, cw) {

            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                if (name.equals("a")) {
                    return null;
                }
                return super.visitField(access, name, descriptor, signature, value);
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if (name.equals("test01")) {
                    return null;
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv, 0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(), Application.class, bytesModifield);

    }
        public static void main(String[] args) throws Exception {
        	new E_removeContent().removeContentByCoreAPI();
    }
}

字节码

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/E_removeContent_Application.class
  Last modified 2020-7-1; size 390 bytes
  MD5 checksum 3ad649468cd26da2758944804ce99b0b
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               b
   #7 = Utf8               I
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = NameAndType        #8:#9          // "":()V
  #11 = Methodref          #4.#10         // java/lang/Object."":()V
  #12 = Utf8               a
  #13 = NameAndType        #12:#7         // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #6:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test02
  #20 = Utf8               Code
  #21 = Utf8               LineNumberTable
  #22 = Utf8               LocalVariableTable
  #23 = Utf8               SourceFile
{
  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #11                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

tree API

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

import static org.objectweb.asm.Opcodes.ASM5;

public class E_removeContent {
    public void removeContentByTreeAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassNode cn = new ClassNode();
        cr.accept(cn, ASM5);
        // 删除 a
        List<FieldNode> fields = cn.fields;
        for (int i = 0; i < fields.size(); i++) {
            FieldNode fn = fields.get(i);
            if("a".equals(fn.name)){
                fields.remove(fn);
            }
        }

        // 删除 method
        List<MethodNode> methods = cn.methods;
        for (int i = 0; i < methods.size(); i++) {
            MethodNode mn = methods.get(i);
            if("test01".equals(mn.name)){
                methods.remove(mn);
            }
        }


        // 打印
        ClassWriter cw = new ClassWriter(0);
        cn.accept(cw);
        byte[] bytesModified = cw.toByteArray();
        Tool.save(this.getClass(), Application.class, bytesModified);
    }

    public static void main(String[] args) throws Exception {
        new E_removeContent().removeContentByTreeAPI();
    }
}

3.6 修改方法

public void test01()

修改为

public int test01(int n) { 
	test02();
	return a + b + n  ;
	}

注意修改方法的区别是,Code属性中包含字节码指令,还要计算操作数栈栈深,和本地变量。所以Code属性visitCode开始,和visitEnd是结束,调用visitMaxs会自动计算栈大小。

这里困难的是Code属性的操作,不过不用担心,前面的插件有用。

core API

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

import static org.objectweb.asm.Opcodes.ASM5;

public class F_modifyMethod {
    public void modifyMethodByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(0);

        ClassVisitor cv = new ClassVisitor(ASM5, cw) {


            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                if (name.equals("test01")) {
                    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, name, "(I)I", null, exceptions);
                    mv.visitCode();
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/Application", "test02", "()V", false);
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                    mv.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "a", "I");
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                    mv.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "b", "I");
                    mv.visitInsn(Opcodes.IADD);
                    mv.visitVarInsn(Opcodes.ILOAD, 1);
                    mv.visitInsn(Opcodes.IADD);
                    mv.visitInsn(Opcodes.IRETURN);
                    mv.visitMaxs(2, 2);
                    mv.visitEnd();

                    return mv;
                }
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv, 0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(), Application.class, bytesModifield);

    }
    public static void main(String[] args) throws Exception {
        new F_modifyMethod().modifyMethodByCoreAPI();
    }
}

字节码

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/F_modifyMethod_Application.class
  Last modified 2020-7-1; size 497 bytes
  MD5 checksum 9791aab52aff864e3abc02c4d8fb65b3
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // "":()V
  #12 = Methodref          #4.#11         // java/lang/Object."":()V
  #13 = NameAndType        #6:#7          // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #8:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test01
  #20 = Utf8               (I)I
  #21 = Utf8               test02
  #22 = NameAndType        #21:#10        // test02:()V
  #23 = Methodref          #2.#22         // com/Application.test02:()V
  #24 = Utf8               Code
  #25 = Utf8               LineNumberTable
  #26 = Utf8               LocalVariableTable
  #27 = Utf8               SourceFile
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public int test01(int);
    descriptor: (I)I
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=2
         0: aload_0
         1: invokevirtual #23                 // Method test02:()V
         4: aload_0
         5: getfield      #14                 // Field a:I
         8: aload_0
         9: getfield      #16                 // Field b:I
        12: iadd
        13: iload_1
        14: iadd
        15: ireturn
        16: return
      LineNumberTable:
        line 9: 16
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           16       1     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

用反编译软件查看,符合预期。

package com;

public class Application {
    public int a = 0;
    public int b = 1;

    public Application() {
    }

    public int test01(int var1) {
        this.test02();
        return this.a + this.b + var1;
    }

    public void test02() {
    }
}

tree API

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

import static org.objectweb.asm.Opcodes.ASM5;

public class F_modifyMethod {
    public void modifyMethodByTreeAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassNode cn = new ClassNode();
        cr.accept(cn, ASM5);

        //new 方法
        MethodNode newmn = new MethodNode(Opcodes.ACC_PUBLIC, "test01", "(I)I", null, null);
        newmn.visitCode();
        newmn.visitVarInsn(Opcodes.ALOAD, 0);
        newmn.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/Application", "test02", "()V", false);
        newmn.visitVarInsn(Opcodes.ALOAD, 0);
        newmn.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "a", "I");
        newmn.visitVarInsn(Opcodes.ALOAD, 0);
        newmn.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "b", "I");
        newmn.visitInsn(Opcodes.IADD);
        newmn.visitVarInsn(Opcodes.ILOAD, 1);
        newmn.visitInsn(Opcodes.IADD);
        newmn.visitInsn(Opcodes.IRETURN);
        newmn.visitMaxs(2, 2);
        newmn.visitEnd();

        // 替换
        List<MethodNode> methods = cn.methods;
        for (int i = 0; i < methods.size(); i++) {
            MethodNode mn = methods.get(i);
            if ("test01".equals(mn.name)) {
                methods.set(i,newmn);
                break;
            }

        }


        // 打印
        ClassWriter cw = new ClassWriter(0);
        cn.accept(cw);
        byte[] bytesModified = cw.toByteArray();
        Tool.save(this.getClass(), Application.class, bytesModified);
    }

    public static void main(String[] args) throws Exception {
        new F_modifyMethod().modifyMethodByTreeAPI();
    }
}

3.7 AdviceAdapter的使用

AdviceAdapter是一个抽象类,继承自MethodVisitor,可以很方便的在方法前和方法后插入代码。核心的两个方法如下

  • onMethodEnter方法开始或者构造器方法中父类的构造器调用以后被回调
  • onMethodExit:正常退出和异常退出时被调用。正常退出指的是遇到RETURN、ARETURN、LRETURN等方法正常返回的情况。异常退出指的是遇到ATHROW指令,有异常抛出方法返回的情况。比如opcode == Opcodes.ATHROW

现在就是为方法调用添加进入和退出方法时的打印。
原始方法

public void test01() {
        System.out.println("mid");
    }

修改后

 public void test01() {
        System.out.println("enter method");
        System.out.println("mid");
        System.out.println("out method -- normal ");
}

core API

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;

import static org.objectweb.asm.Opcodes.ASM5;
import static org.objectweb.asm.Opcodes.ASM7;

public class G_adviceMethod {
    public void adviceMethodByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new ClassVisitor(ASM7, cw) {


            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);

                if (!"test01".equals(name)) {
                    return mv;
                }
                // 修改test01 进入方法
                return new AdviceAdapter(ASM7, mv, access, name, descriptor) {
                    @Override
                    protected void onMethodEnter() {
                        super.onMethodEnter();
                        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("enter method");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

                    }

                    // 退出方法
                    @Override
                    protected void onMethodExit(int opcode) {
                        super.onMethodExit(opcode);
                        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        // 异常
                        if (opcode == Opcodes.ATHROW) {
                            mv.visitLdcInsn("out method -- exception");
                        } else {
                            mv.visitLdcInsn("out method -- normal ");
                        }

                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                    }
                };
            }

        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv, 0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(), Application.class, bytesModifield);

    }
   public static void main(String[] args) throws Exception {
        G_adviceMethod advice = new G_adviceMethod();
        // 修改方法
        advice.adviceMethodByCoreAPI();
        // 调用类
        new Application().test01();
    }
}

bytecode
开始和结束加入了额外的方法

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/G_adviceMethod_Application.class
  Last modified 2020-7-1; size 773 bytes
  MD5 checksum 0700ead97bf0b298541bce4618de4e4e
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // "":()V
  #12 = Methodref          #4.#11         // java/lang/Object."":()V
  #13 = NameAndType        #6:#7          // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #8:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test01
  #20 = Utf8               java/lang/System
  #21 = Class              #20            // java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #25 = Fieldref           #21.#24        // java/lang/System.out:Ljava/io/PrintStream;
  #26 = Utf8               enter method
  #27 = String             #26            // enter method
  #28 = Utf8               java/io/PrintStream
  #29 = Class              #28            // java/io/PrintStream
  #30 = Utf8               println
  #31 = Utf8               (Ljava/lang/String;)V
  #32 = NameAndType        #30:#31        // println:(Ljava/lang/String;)V
  #33 = Methodref          #29.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #34 = Utf8               mid
  #35 = String             #34            // mid
  #36 = Utf8               out method -- normal
  #37 = String             #36            // out method -- normal
  #38 = Utf8               test02
  #39 = Utf8               Code
  #40 = Utf8               LineNumberTable
  #41 = Utf8               LocalVariableTable
  #42 = Utf8               SourceFile
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #27                 // String enter method
         5: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #35                 // String mid
        13: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        19: ldc           #37                 // String out method -- normal
        21: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        24: return
      LineNumberTable:
        line 8: 8
        line 9: 16
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      17     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"
ring;)V
        40: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        43: ldc           #27                 // String enter method
        45: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        48: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        51: ldc           #27                 // String enter method
        53: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        56: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        59: ldc           #35                 // String mid
        61: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        64: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        67: ldc           #37                 // String out method -- normal
        69: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        72: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        75: ldc           #37                 // String out method -- normal
        77: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        80: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        83: ldc           #37                 // String out method -- normal
        85: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        88: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        91: ldc           #37                 // String out method -- normal
        93: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        96: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        99: ldc           #37                 // String out method -- normal
       101: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       104: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
       107: ldc           #37                 // String out method -- normal
       109: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       112: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
       115: ldc           #37                 // String out method -- normal
       117: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       120: return
      LineNumberTable:
        line 8: 56
        line 9: 64
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           56      65     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

tree API

涉及MethodVisitor和MethodNode的相互转换。比较麻烦coreAPI比较简单

3.8 try-catch

很显然上一个小节的代码无法在代码抛出未捕获异常时输出err quit,比如把foo代码做细微修改,如下所示。

public void test01() {
    System.out.println("step 1");
    int a = 1 / 0;
    System.out.println("step 2");
}

经过上个例子的字节码改写以后,执行的结果输出如下所示。

java -cp . Application  
enter method
step 1
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Application.test01(Application.java:24)
        at Application.main(Application.java:8)

可以看到并没有如期输出“error exit foo”,因为在字节码中并没有出现显式的ATHROW指令抛出异常,自然无法添加相应的输出语句。为了达到这个效果,需要把方法体用try/finally语句块包裹起来。

这里需要介绍ASM的Label类,与它的英文含义一样,可以给字节码指令地址打标签,标记特定的字节码位置,用于后续跳转等。新增一个Label可以用MethodVisitor的visitLabel方法,如下所示。

Label startLabel = new Label();
mv.visitLabel(startLabel);

前面章节介绍过,JVM的异常处理是通过异常表来实现的,try-catch-finally语句块实际上是标定了异常处理的范围。ASM中可以用visitTryCatchBlock方法来给一段代码块增加异常表,它的方法签名如下所示。
public void visitTryCatchBlock(Label start, Label end, Label handler, String type)
其中start、end表示异常表开始和结束的位置,handler表示异常发生后需要跳转到哪里继续执行,可以理解为catch语句块开始的位置,type是异常的类型。

为了给整个方法体包裹try-catch语句,start Label应该放在方法visitCode之后,end Label则放在visitMaxs调用之前,代码如下所示。

core API

源码

public void test01() {
        System.out.println("mid");
    }

结果

    public void test01() {
        try {
            System.out.println("enter method");
            System.out.println("mid");
            System.out.println("normal exit method");
        } catch (Throwable var2) {
            System.out.println("error exit method");
            throw var2;
        }
    }

实现

package com;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import static org.objectweb.asm.Opcodes.ASM7;

public class H_addTrycatch {

    public void addTrycatchByCoreAPI() throws Exception {
        ClassReader cr = Tool.getClassReader();
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new ClassVisitor(ASM7, cw) {


            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);

                if (!"test01".equals(name)) {
                    return mv;
                }
                // 开始的标签

                // 修改test01 进入方法
                return new AdviceAdapter(ASM7, mv, access, name, descriptor) {
                    Label startLabel = new Label();
                    Label endLable = new Label();

                    @Override
                    protected void onMethodEnter() {
                        super.onMethodEnter();
                        // mark start
                        mv.visitLabel(startLabel);
                        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("enter method");
                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

                    }

                    @Override
                    public void visitMaxs(int maxStack, int maxLocals) {
//                        // mark end
                        Label endLable = new Label();
//                        /**
//                         * startLabel 开始的标签
//                         * endLable 结束的标签
//                         * endLable 跳转的位置
//                         */
                        mv.visitTryCatchBlock(startLabel, endLable, endLable, null);
                        mv.visitLabel(endLable);
//                        // 打印异常
                        finallyBlock(Opcodes.ATHROW);
//                        // 抛出异常
                        mv.visitInsn(ATHROW);
                        super.visitMaxs(maxStack, maxLocals);

                    }

                    private void finallyBlock(int opcode) {
                        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        if (opcode == Opcodes.ATHROW) {
                            mv.visitLdcInsn("error exit method");
                        } else {
                            mv.visitLdcInsn("normal exit method");
                        }

                        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

                    }

                    // 退出方法
                    @Override
                    protected void onMethodExit(int opcode) {
                        super.onMethodExit(opcode);

                        finallyBlock(opcode);
                    }
                };
            }

        };
        //cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
        cr.accept(cv, 0);
        byte[] bytesModifield = cw.toByteArray();

        Tool.save(this.getClass(), Application.class, bytesModifield);

    }


    public static void main(String[] args) throws Exception {
        H_addTrycatch tc = new H_addTrycatch();
        // 修改方法
        tc.addTrycatchByCoreAPI();
        // 调用类
        new Application().test01();
    }
}

字节码

Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/H_addTrycatch_Application.class
  Last modified 2020-7-1; size 767 bytes
  MD5 checksum d0368db93d0ee61fa5b689cc76356c0a
  Compiled from "Application.java"
public class com.Application
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/Application
   #2 = Class              #1             // com/Application
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Application.java
   #6 = Utf8               a
   #7 = Utf8               I
   #8 = Utf8               b
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = NameAndType        #9:#10         // "":()V
  #12 = Methodref          #4.#11         // java/lang/Object."":()V
  #13 = NameAndType        #6:#7          // a:I
  #14 = Fieldref           #2.#13         // com/Application.a:I
  #15 = NameAndType        #8:#7          // b:I
  #16 = Fieldref           #2.#15         // com/Application.b:I
  #17 = Utf8               this
  #18 = Utf8               Lcom/Application;
  #19 = Utf8               test01
  #20 = Utf8               java/lang/System
  #21 = Class              #20            // java/lang/System
  #22 = Utf8               out
  #23 = Utf8               Ljava/io/PrintStream;
  #24 = NameAndType        #22:#23        // out:Ljava/io/PrintStream;
  #25 = Fieldref           #21.#24        // java/lang/System.out:Ljava/io/PrintStream;
  #26 = Utf8               enter method
  #27 = String             #26            // enter method
  #28 = Utf8               java/io/PrintStream
  #29 = Class              #28            // java/io/PrintStream
  #30 = Utf8               println
  #31 = Utf8               (Ljava/lang/String;)V
  #32 = NameAndType        #30:#31        // println:(Ljava/lang/String;)V
  #33 = Methodref          #29.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #34 = Utf8               mid
  #35 = String             #34            // mid
  #36 = Utf8               normal exit method
  #37 = String             #36            // normal exit method
  #38 = Utf8               error exit method
  #39 = String             #38            // error exit method
  #40 = Utf8               java/lang/Throwable
  #41 = Class              #40            // java/lang/Throwable
  #42 = Utf8               test02
  #43 = Utf8               Code
  #44 = Utf8               LineNumberTable
  #45 = Utf8               LocalVariableTable
  #46 = Utf8               StackMapTable
  #47 = Utf8               SourceFile
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public int b;
    descriptor: I
    flags: ACC_PUBLIC

  public com.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."":()V
         4: aload_0
         5: iconst_0
         6: putfield      #14                 // Field a:I
         9: aload_0
        10: iconst_1
        11: putfield      #16                 // Field b:I
        14: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      15     0  this   Lcom/Application;

  public void test01();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #27                 // String enter method
         5: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #35                 // String mid
        13: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        19: ldc           #37                 // String normal exit method
        21: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        24: return
        25: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        28: ldc           #39                 // String error exit method
        30: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        33: athrow
      Exception table:
         from    to  target type
             0    25    25   any
      StackMapTable: number_of_entries = 1
        frame_type = 89 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
      LineNumberTable:
        line 8: 8
        line 9: 16
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      17     0  this   Lcom/Application;

  public void test02();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/Application;
}
SourceFile: "Application.java"

4. bytebuddy的示例

直接看bytebuddy官网用处不大,介绍文档还是比较简陋的。我是文档和源码比对者读的。

bytebuddy是基于ASM写的一个工具。也是用来对类进行加工的。很多API的模型继承与ASM,直接阅读bytebuddy官方文档和源码是十分费解的。建议先学习ASM

下面用bytebuddy演示一个等价的例子。

你可能感兴趣的:(字节码)