Javassist如何操作Java 字节码

作者:码恋 时间:2021-08-09 08:21:28 

一、开篇

说起 AOP 小伙伴们肯定很熟悉,无论是 JDK * 或者是 CGLIB 等,其底层都是通过操作 Java 字节码来实现代理。常用的一些操作字节码的技术有 ASM、AspectJ、Javassist 等。

ASM 其设计和实现是尽可能小而且快,更专注于性能。它在指令的层面来操作,所以使用它需要对 JVM 的指令有所了解,门槛较高,CGLIB 就使用了 ASM 技术。
AspectJ 扩展了 Java 语言,定义了一系列 AOP 语法,在 JVM 中运行需要使用特定的编译器生成遵守 Java 字节码规范的 Class 文件,Spring AOP 使用了 AspectJ 。
Javassist 直接使用 Java 编码的形式操作字节码,简单易上手,性能高于反射,相比于 ASM 稍低。

二、Javassist 常用类

Javassist 抽象出一个 ClassPool 对象来操作 Java 类,可以通过 ClassPool.getDefault() 来获取默认的 ClassPool 。常用的对象:

CtClass:代表一个 Class 的实例,可以通过类的全限定名来获取 CtClass 对象,其中包含了对 Class 的各种操作。
ClassPool:通过 HashTable 保存了路径下的 CtClass 信息,key为类的全限定名称,value 为类名对应的 CtClass 对象。
CtMethod、CtField:抽象出类的方法和属性,可以用于定义或修改方法和字段。

三、Javassist 的使用

1、依赖


<dependency>
 <groupId>org.javassist</groupId>
 <artifactId>javassist</artifactId>
 <version>3.27.0-GA</version>
</dependency>

2、代码示例


// 获取默认类池
 ClassPool classPool = ClassPool.getDefault();
 // 1. 创建空类
 CtClass ctClass = classPool.makeClass("com.aysaml.demo.javassist.User");

// 2. 创建 String 类型的 name 字段
 CtField field = new CtField(classPool.get("java.lang.String"), "name", ctClass);
 // 设置字段访问级别 private
 field.setModifiers(Modifier.PRIVATE);
 // 增加字段
 ctClass.addField(field);

// 3. 增加 getter & setter 方法
 ctClass.addMethod(CtNewMethod.getter("getName", field));
 ctClass.addMethod(CtNewMethod.setter("setName", field));

// 4. 增加无参构造方法:其中 $0 表示 this,$1 表示参数
 CtConstructor noArgsCons = new CtConstructor(new CtClass[] {}, ctClass);
 noArgsCons.setBody("{$0.name=\"mark\";}");
 ctClass.addConstructor(noArgsCons);

// 5. 增加有参构造方法
 CtConstructor hasArgsCons =
   new CtConstructor(new CtClass[] {classPool.get("java.lang.String")}, ctClass);
 hasArgsCons.setBody("{$0.name=$1;}");
 ctClass.addConstructor(hasArgsCons);

// 6. 创建方法
 CtMethod method = new CtMethod(CtClass.voidType, "printName", new CtClass[] {}, ctClass);
 method.setBody("{System.out.println($0.name);}");
 ctClass.addMethod(method);

// 7. 生成类文件:可指定路径,默认为当前项目根目录
 ctClass.writeFile();

// 8. 创建类实例
 Object person = ctClass.toClass().newInstance();

3、如何实现类似 AOP 的功能

由上可见,Javassist 对于编程化的操作字节码是很简单易懂的,我们以在方法的开头结尾打印信息为例:


public class Cat {

/** 记录喵喵喵的次数 */
private int num;

public void miao() {
 this.num++;
}
}

我们要在 miao( ) 方法的前增加声音输出:


public static void main(String[] args) throws NotFoundException, CannotCompileException {
 ClassPool classPool = ClassPool.getDefault();
 // 获取 Cat 类的 CtClass 对象
 CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat");
 // 获取 miao( ) 方法
 CtMethod method = catClass.getDeclaredMethod("miao");
 method.insertBefore("System.out.println(\"miao~\");");
 // 加载修改过的类,注意必须要保证调用前这个类没有被加载过
 catClass.toClass();
 //测试
 Cat cat = new Cat();
 cat.miao();
}

注意到,在使用 catClass.toClass() 加载被修改过的类时,强调必须保证在调用前这个类没有被加载过,否则会报 attempted duplicate class definition for name 异常。

我们知道一个类是不能被一个类加载器加载两次的,所以为了解决这个问题,需要制定一个没有加载过该类的 Classloader,Javassist 提供了一个 ClassLoader ,如下:


public class Cat {

/** 记录喵喵喵的次数 */
private int num;

public void miao() {
 System.out.println("调用了 miao 方法");
 this.num++;
}

public static void main(String[] args) throws Exception{
 ClassPool classPool = ClassPool.getDefault();
 // 获取 Cat 类的 CtClass 对象
 CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat");
 // 获取 miao( ) 方法
 CtMethod method = catClass.getDeclaredMethod("miao");
 method.insertBefore("System.out.println(\"miao~\");");
 // 重新设置一个 Classloader
 Loader classLoader = new Loader(classPool);
 Class clazz = classLoader.loadClass("com.aysaml.demo.javassist.Cat");
 // 调用修改过的类的方法
 clazz.getDeclaredMethod("miao").invoke(clazz.newInstance());
}
}

执行结果为:

Javassist如何操作Java 字节码

四、结语

关于 Javassist 暂时就说这么多了,更多使用方法参考官方 github wiki :

来源:https://www.tuicool.com/articles/QJBjmaR

标签:Javassist,字节码,Java
0
投稿

猜你喜欢

  • SpringBoot多线程进行异步请求的处理方式

    2021-11-10 10:48:30
  • 浅谈Java变量的初始化顺序详解

    2023-07-26 03:23:17
  • Java 读写锁源码分析

    2021-08-31 01:48:54
  • 在springboot中实现个别bean懒加载的操作

    2023-11-25 09:44:11
  • Maven 生成打包可执行jar包的方法步骤

    2023-01-02 14:53:15
  • 解析Java中未被捕获的异常以及try语句的嵌套使用

    2022-10-18 20:03:48
  • Java数组索引异常产生及解决方案

    2023-11-05 16:52:27
  • Java 超详细讲解ThreadLocal类的使用

    2021-11-13 05:58:29
  • SpringBoot深入分析运行原理与功能实现

    2022-01-03 14:48:43
  • Android RecyclerView基本使用详解

    2023-07-24 21:13:30
  • SpringBoot自动装配原理详解

    2023-07-26 08:44:46
  • 代码分析Android消息机制

    2023-07-26 09:44:44
  • BeanDefinition基础信息讲解

    2022-03-23 23:48:37
  • Android实现摇一摇功能

    2023-07-23 20:21:11
  • java Mail邮件接收工具类

    2022-04-24 14:17:17
  • javax.persistence中@Column定义字段类型方式

    2021-12-03 21:21:44
  • Java 归并排序算法、堆排序算法实例详解

    2023-11-25 09:43:25
  • 解决mybatis批量更新出现SQL报错问题

    2023-11-29 04:12:47
  • Spring JDK动态 代理实现过程详解

    2023-11-16 19:42:15
  • 通过Java带你了解网络IO模型

    2022-12-25 10:59:22
  • asp之家 软件编程 m.aspxhome.com