详解Java中Javassist的使用

 更新时间:2023年04月03日 16:41:52   作者:胡安民  
常用的一些操作字节码的技术有 ASM、AspectJ、Javassist 等。本文主要为大家介绍了Javassist使用的相关知识,感兴趣的小伙伴可以了解一下

开篇

说起 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 的使用

依赖

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

代码示例

// 获取默认类池
  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();

如何实现类似 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());
 }
}

执行结果为:

到此这篇关于详解Java中Javassist的使用的文章就介绍到这了,更多相关Java Javassist内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java编程异常处理最佳实践【推荐】

    Java编程异常处理最佳实践【推荐】

    这篇文章主要介绍了Java编程异常处理最佳实践【推荐】,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • Springboot项目删除项目同步target文件问题解决方案

    Springboot项目删除项目同步target文件问题解决方案

    这篇文章主要介绍了Springboot项目删除项目同步target文件问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • spring security与corsFilter冲突的解决方案

    spring security与corsFilter冲突的解决方案

    这篇文章主要介绍了spring security与corsFilter冲突的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Maven之远程仓库的配置详解

    Maven之远程仓库的配置详解

    这篇文章主要介绍了Maven之远程仓库的配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Spring Security OAuth2 token权限隔离实例解析

    Spring Security OAuth2 token权限隔离实例解析

    这篇文章主要介绍了Spring Security OAuth2 token权限隔离实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java中的命名与目录接口JNDI基本操作方法概览

    Java中的命名与目录接口JNDI基本操作方法概览

    这篇文章主要介绍了Java中的命名与目录接口JNDI基本操作方法概览,JNDI提供统一的客户端API使得Java应用程序可以和这些命名服务和目录服务之间进行交互,需要的朋友可以参考下
    2016-03-03
  • Java中与数字相关的常用类的用法详解

    Java中与数字相关的常用类的用法详解

    在我们的代码中,经常会遇到一些数字&数学问题、随机数问题、日期问题和系统设置问题等,为了解决这些问题,Java给我们提供了多个处理相关问题的类,比如Number类、Math类、Random类等等,本篇文章我们先从Number数字类和Math数学类学起
    2023-05-05
  • 解决idea中servlet报红问题

    解决idea中servlet报红问题

    这篇文章主要介绍了解决idea中servlet报红问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • SpringData JPA 如何搭建 xml的配置方式

    SpringData JPA 如何搭建 xml的配置方式

    这篇文章主要介绍了SpringData JPA 如何搭建 xml的配置方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-12-12
  • Intellij Idea 多模块Maven工程中模块之间无法相互引用问题

    Intellij Idea 多模块Maven工程中模块之间无法相互引用问题

    这篇文章主要介绍了Intellij Idea 多模块Maven工程中模块之间无法相互引用问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论