Javassist如何操作Java 字节码

所属分类: 软件编程 / java 阅读数: 63
收藏 0 赞 0 分享

一、开篇

说起 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 暂时就说这么多了,更多使用方法参考官方 github wiki :

以上就是Javassist如何操作Java 字节码的详细内容,更多关于Javassist 操作Java 字节码的资料请关注脚本之家其它相关文章!

更多精彩内容其他人还在看

Java输入输出流复制文件所用时间对比

这篇文章主要介绍了Java输入输出流复制文件所用时间对比的相关资料,非常不错,具有参考解决价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Java线程中start和run方法全面解析

这篇文章主要介绍了Java线程中start和run方法的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Java的JSON处理器fastjson使用方法详解

下面小编就为大家带来一篇Java的JSON处理器fastjson使用方法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

Java 二维码,QR码,J4L-QRCode 的资料整理

本文主要介绍Java 中二维码,QR码,J4L-QRCode,这里整理了详细的资料供大家学习参考关于二维码的知识,有需要的小伙伴可以参考下
收藏 0 赞 0 分享

java哈夫曼树实例代码

这篇文章主要为大家介绍了java哈夫曼树实例代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Android读取本地或网络图片并转换为Bitmap

这篇文章主要为大家详细介绍了Android读取本地或网络图片,并转换为Bitmap,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Java日期时间操作的方法

这篇文章主要为大家详细介绍了Java日期时间操作的一些方法,获得Calendar,定义日期/时间的格式等,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

java 获取路径的各种方法(总结)

下面小编就为大家带来一篇java 获取路径的各种方法(总结)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

java数据结构与算法之奇偶排序算法完整示例

这篇文章主要介绍了java数据结构与算法之奇偶排序算法,较为详细的分析了奇偶算法的原理并结合完整示例形式给出了实现技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

java数据结构与算法之双向循环队列的数组实现方法

这篇文章主要介绍了java数据结构与算法之双向循环队列的数组实现方法,结合实例形式分析了双向循环队列的原理与数组实现技巧,并附带说明了该算法的用途,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多