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 暂时就说这么多了,更多使用方法参考官方 github wiki :
来源:https://www.tuicool.com/articles/QJBjmaR


猜你喜欢
- 本文实例讲述了Android编程简单实现九宫格。分享给大家供大家参考,具体如下:实现的步骤1. 一个整体的容器部分。就是上图中包括整个图片项
- 在 C语言中,占位符是一种用于格式化输出的特殊字符,通常用于 printf() 等输出函数中,用于指定输出的格式和内容。在本文中,我们将详细
- 本篇介绍线性图标RecyclerLineChart 的绘制,对于图表的公共部分X、Y轴,背景Board等的绘制先前章节已经有过介绍,这里不再
- BottomBarBottomBar是Github上的一个开源框架,因为从1.3.3开始不支持fragments了,要自己配置,弄了很久,不
- 1、字符串数字之间的转换(1)string --> char * string str("OK");
- 实现效果为一个小球接触左右侧时,会反向的运动。import javafx.application.Application;import ja
- 最近研究了一下android摄像头开发相关的技术,也看了Google提供的Camera2Basic调用示例,以及网上一部分代码,但都是在Te
- 由于公司同是使用.NET和JAVA,而且各个服务集使用接口来进行通信,因此某些例如清算系统、收银台之类的安全性比较高的系统会使用RSA进行加
- 今天写一个小程序有一个给图片加上阴影的需求,记得WPF的Effect中就有阴影特效,就打算用它了。代码如下:using (var image
- 本文实例为大家分享了java实现双色球抽奖的具体代码,供大家参考,具体内容如下实现双色球先考虑整体思路:1.随机生成7位数的数组为大奖号码(
- 人机交互过程中,当我们需要机器给我们反馈不确定的数字结果时,就会需要用到随机数了,那么,在Java中,我们应当如何来生成并使用随机数呢?一、
- 本文实例讲解了iOS从背景图中取色的代码,分享给大家供大家参考,具体内容如下实现代码:void *bitmapData; //内存空间的指针
- https://www.jb51.net/article/152879.htm上节,我们明白了proc文件系统的作用,接下来我们在已经写好的
- 1、任何的高并发,请求总是会有一个顺序的2、java的队列的数据结构是先进先出的取值顺序3、BlockingQueue类(线程安全)(使用方
- 最近在补看《C++ Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑。C++面试过
- 具体解释请仔细看注释里已经讲解的很细致了,这里就不多废话了using UnityEngine;using System.Collection
- 环境信息名称版本号Spring Boot2.4.5Idea2021.3.2服务端实现导入依赖<dependency>  
- 上篇文章给大家介绍了springboot对接第三方微信授权及获取用户的头像和昵称等等1 账户注销1.1 在SecurityConfig中加入
- 本文实例讲述了java和c#使用hessian通信的方法,是非常实用的技巧。分享给大家供大家参考。具体分析如下:首先,hessian主页为:
- 基本概念 * (Listener): * 用于监听web应用中某些对象、信息的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处