1.摘要

javassist是一个"Java字节码"操作类库.

 

2.应用场景

1. 读取.class文件到内存, 
        新增或删除字段或方法,
        修改字段或方法的 访问级别, 是否final, 是否static, 是否abstract
        改换类名,
        改换类继承的接口或父类
        ...
   然后写回.class文件.
2. ClassLoader加载某个.class文件时, 动态的在方法的前后加点字节码, 实现动态代理及AOP的功能
 

3.API模型

javassist提供了, 如下两种级别的API
1.高级别API
    无需"Java字节码"相关知识, 就可操作"Java字节码", 低性能, 易使用. 

javassist如何使用 javassist包_javassist

    
2.低级别API
    需要"Java字节码"相关知识, 才可操作"Java字节码", 高性能, 难使用.
    

javassist如何使用 javassist包_Test_02

 

源码实现上, "高级别API" 依赖了 "低级别API"

 

4.代码示例

高级别API的简单实用

package demo.javassist;

import javassist.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;


/**
 * 深入理解JVM字节码 - [6.2]Javassist介绍
 *
 * 这本书, 主要介绍了, 高级别API的使用方式
 */
@DisplayName("深入理解JVM字节码 - [6.2]Javassist介绍")
public class Demo4Book {

    @Test
    @DisplayName("创建类")
    public void testMakeClass() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.makeClass("demo.MyMain");
        cc.writeFile();
    }

    @Test
    @DisplayName("新增字段")
    public void testAddField() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");

        CtField cf = new CtField(CtClass.intType, "i", cc);
        cf.setModifiers(Modifier.PRIVATE);
        cc.addField(cf);

        cc.writeFile();
    }

    @Test
    @DisplayName("新增方法")
    public void testAddMethod() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");

        // 参数依次为 返回类型, 方法名称, 参数类型, 方法所属类
        CtMethod cm = new CtMethod(CtClass.intType, "foo",
                new CtClass[]{CtClass.intType, CtClass.intType}, cc);
        // 设定方法的 修饰符(public, static等等), 具体参考Modifier
        cm.setModifiers(Modifier.PUBLIC);
        cc.addMethod(cm);

        cc.writeFile();
    }

    @Test
    @DisplayName("设定方法体")
    public void testMethodSetBody() throws Throwable{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");

        // $0为this, $1为第一个方法参数, $2位第二个方法参数
        cm.setBody("{System.out.println(\"$0 = this \" + $0); return $1 + $2 + i; }");

        cc.writeFile();
    }

    @Test
    @DisplayName("方法体-前加代码")
    public void testMethodInsertBefore() throws Throwable{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");

        cm.insertBefore("System.out.println(\" method start2 \");");
        cm.insertBefore("System.out.println(\" method start1 \");");

        cc.writeFile();
    }

    @Test
    @DisplayName("方法体-后加代码")
    public void testMethodInsertAfter() throws Throwable{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");

        cm.insertAfter("System.out.println(\" method end1 \");");
        cm.insertAfter("System.out.println(\" method end2 \");");

        cc.writeFile();
    }

    @Test
    @DisplayName("$开头的特殊标识符列表")
    public void test$Args() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");

        cm.setBody("{return 0;}");

        // 方法参数数组  // new Object[]{new Integer(var1), new Integer(var2)}
        cm.insertAfter("System.out.println(\" $args = \" + $args);");
        // 返回值       // 0
        cm.insertAfter("System.out.println(\" $_ \" + $_);");
        // 参数类型数组  // Desc.getParams("(II)I") // 类型描述符
        cm.insertAfter("System.out.println(\" $sig \" + $sig);");
        // 返回类型     // Desc.getType("I")
        cm.insertAfter("System.out.println(\" $type \" + $type);");
        // 当前类型     // Desc.getClazz("demo.MyMain")
        cm.insertAfter("System.out.println(\" $class \" + $class);");
        // 所有参数     // this.foo(var1, var2);
        cm.insertAfter("foo($$);");
        // $r TODO
        // $w TODO
        // $cflow 递归调用深度

        cc.writeFile();
    }

    @Test
    @DisplayName("添加一个fibonacci方法")
    public void testAddFibonacci() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");

        // 参数依次为 返回类型, 方法名称, 参数类型, 方法所属类
        CtMethod cm = new CtMethod(CtClass.intType, "fibonacci",
                new CtClass[]{CtClass.intType}, cc);
        // 设定方法的 修饰符(public, static等等), 具体参考Modifier
        cm.setModifiers(Modifier.PUBLIC);
        cm.setBody("if ($1 <=1) {return $1;} " +
                     "else {return fibonacci($1-1) + fibonacci($1-2);}");
        cc.addMethod(cm);

        cc.writeFile();
    }

    @Test
    @DisplayName("$cflow-递归调用深度")
    public void test$cflow() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");

        // public static Cflow _cflow$0 = new Cflow();
        // public class Cflow extends ThreadLocal<Cflow.Depth>
        // 实现方式, 线程本地变量
        CtMethod cm = cc.getMethod("fibonacci", "(I)I");
        cm.useCflow("fibonacci");
        cm.insertBefore(
                "if ($cflow(fibonacci) == 0) {" +
                        "System.out.println(\" fibonacci init \" + $1);" +
                     "}"
        );


        cc.writeFile();
    }

    @Test
    @DisplayName("除零异常")
    public void testDivByZero() throws Throwable{
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");

        cm.setBody("{int a = 1; int b = 0; int c = a / b; return $1 + $2;}");

        cc.writeFile();
    }

    @Test
    @DisplayName("除零异常, insertAfter finally")
    public void testInsertAfterFinally() throws Throwable {
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");

        CtClass cc = cp.get("demo.MyMain");
        CtMethod cm = cc.getMethod("foo", "(II)I");
        cm.insertAfter("System.out.println(\" $_ \" + $_);", true);

        cc.writeFile();
    }
}

低级别API的简单实用

package demo.javassist;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ClassFile;
import javassist.bytecode.FieldInfo;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.*;


/**
 * Javassist官方参考
 * 1.官网: http://www.javassist.org/
 * 2.入门指南: http://www.javassist.org/tutorial/tutorial.html
 * 3.在线API文档: http://www.javassist.org/html/index.html
 */
@DisplayName("Javassist官方指南")
public class Demo4Tutorial {

    /**
     * 三种获取ClassFile的方式
     * @throws Throwable
     */
    @Test
    @DisplayName("三种获取ClassFile的方式")
    public void testThreeWayGetClassFile() throws Throwable {
        // 方式1(高级别API): 从CtClass获取ClassFile
        ClassPool cp = ClassPool.getDefault();
        cp.insertClassPath(".");
        CtClass cc = cp.get("demo.MyMain");
        ClassFile cf = cc.getClassFile();

        // 方式2(低级别API): 直接从数据流构建ClassFile
        BufferedInputStream fin = new BufferedInputStream(new FileInputStream("./demo/MyMain.class"));
        cf = new ClassFile(new DataInputStream(fin));

        // 方式3(低级别API): 手动构建ClassFile
        cf = new ClassFile(false, "demo.Foo", null);

        cf.setInterfaces(new String[] { "java.lang.Cloneable" });

        FieldInfo f = new FieldInfo(cf.getConstPool(), "width", "I");
        f.setAccessFlags(AccessFlag.PUBLIC);
        cf.addField(f);

        cf.write(new DataOutputStream(new FileOutputStream("./demo/Foo.class")));
    }

    /**
     * ClassFile读取和写入用到的核心方法
     * 读取: ClassFile.read
     * 写入: ClassFile.write
     *
     * 熟悉这两个方法, 对Java字节码结构的理解, 帮助会非常大!!!
     */
    @Test
    @DisplayName("ClassFile读取和写入")
    public void testReadAndWriteClassFile() throws Throwable {
        // ClassFile读取
        BufferedInputStream fin = new BufferedInputStream(new FileInputStream("./demo/MyMain.class"));
        ClassFile cf = new ClassFile(new DataInputStream(fin));

        // ClassFile写入
        cf.write(new DataOutputStream(new FileOutputStream("./demo/MyMainCopy.class")));
    }

}

 

5.相关参考

一. Javassist官方参考
    1.官网: http://www.javassist.org/
      可以下载发布包, 里面有(Jar包, 源码, 使用样例, 入门指南).
    2.入门指南: http://www.javassist.org/tutorial/tutorial.html
      就三页, 写的较为全面, 但是比较琐碎, 容易陷入细节泥潭
      建议先跑几个demo, 按照如上模型, 调试下源码 (源码比较简单), 然后再来看
    3.在线API文档: http://www.javassist.org/html/index.html
      JavaDoc文档
二. [书籍] 深入理解JVM字节码(张亚) [6.2]Javassist介绍
     围绕字节码相关的知识, 介绍都较为全面

三. [书籍] 深入理解Java虚拟机(周志明)