Javassist框架研究
上一篇我们简单介绍了ASM,这一篇我们介绍一下Javassist。javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的Shigeru Chiba (千叶滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类(这一点与ASM相比用起来爽很多,但是性能上还是ASM更快)。
Javassist中最为重要的是ClassPool,CtClass ,CtMethod 以及 CtField这几个类。
ClassPool: 一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。需要的注意的是当CtClass数量过多时,会占用大量的内存,API中给出的解方案是有意识的调用CtClass的detach方法释放内存。
- getDefault : 返回默认的ClassPool 是单例模式的,一般通过该方法创建我们的ClassPool;
- appendClassPath, insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;
- toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;
- get , getCtClass : 根据类路径名获取该类的CtClass对象,用于后续的编辑。
CtClass: 表示一个类,这些CtClass对象可以从ClassPool获得。
CtMethods: 表示类中的方法。
- insertBefore : 在方法的起始位置插入代码;
- insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
- insertAt : 在指定的位置插入代码;
- setBody : 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
- make : 创建一个新的方法。
需要注意的是 传入insertBefore(),insertAfter(),addCatch()和insertAt()的字符串对象被Javassist包含的编译器编译。因此编译器支持语言扩展,专有的以$开头的标识符有特殊的意义:
$0, $1, $2,… | 参数数组,$args的类型为object[] |
---|---|
$args | this和实际的参数 |
$$ | 所有实际的参数,例如m($$)与m($1,$2,…)等同 |
$cflow(…) | cflow变量 |
$r | 返回类型。它被用在转换表达式中。 |
$w | 包装类类型。它被用在转换表达式中。 |
$_ | 返回值 |
$sig | 一个java.lang.Class对象数组,表示正式的参数类型。 |
$type | 一个java.lang.Class对象,表示了正式的返回类型。 |
$class | 一个java.lang.Class对象,表示当前编辑的类。 |
$0, $1, $2, …
传入目标方法的参数需要使用$1, $2, … 而不是原始的参数名。$1 表示第一个参数,$2 表示第二个参数等等。这些变量的类型和参数的类型一致。$0 等于this。如果方法是静态的,$0 无法获取。
另外需要注意的是:上面的insertBefore() 和 setBody()中的语句,如果你是单行语句可以直接用双引号,但是有多行语句的情况下,你需要将多行语句用{}括起来。javassist只接受单个语句或用大括号括起来的语句块。
更多详情参考javassist文档.
CtFields :表示类中的字段。
动态生成一个类:
import javassist.*;
import javassist.bytecode.AccessFlag;import java.io.IOException;
import java.lang.reflect.Field;public class Test {public static void genClassMethod() {ClassPool pool = ClassPool.getDefault();// 创建一个public类,假如类已经存在会覆盖原类CtClass ct = pool.makeClass("com.pch.javassist.GenClass");// 让类实现Cloneable接口ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});try {// 获得一个类型为int,名称为id的字段CtField f = new CtField(CtClass.intType, "id", ct);// 将字段设置为publicf.setModifiers(AccessFlag.PUBLIC);// 将字段设置到类上ct.addField(f);// 添加构造函数CtConstructor constructor = CtNewConstructor.make("public GenClass(int id) {this.id = id;}", ct);ct.addConstructor(constructor);// 添加方法CtMethod testM = CtNewMethod.make("public void print(String des){ System.out.println(des);}", ct);ct.addMethod(testM);// 将生成的.class文件保存到磁盘ct.writeFile();// 下面的代码为验证代码Field[] fields = ct.toClass().getFields();System.out.println("属性名称:" + fields[0].getName() + " 属性类型:" + fields[0].getType());} catch (CannotCompileException | NotFoundException | IOException e) {e.printStackTrace();}}public static void main(String[] args) {genClassMethod();}
}
这样我们就动态的生成了一个类。是不是比ASM使用起来简单很多。
注意:使用过程中如果出现以下报错
Exception in thread "main" java.lang.NullPointerExceptionat javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:103)at javassist.util.proxy.DefineClassHelper.toClass3(DefineClassHelper.java:151)at javassist.util.proxy.DefineClassHelper.toClass2(DefineClassHelper.java:134)at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:95)at javassist.ClassPool.toClass(ClassPool.java:1143)at javassist.ClassPool.toClass(ClassPool.java:1106)at javassist.ClassPool.toClass(ClassPool.java:1064)at javassist.CtClass.toClass(CtClass.java:1275)at com.pch.javassist.Test.genClassMethod(Test.java:34)at com.pch.javassist.Test.main(Test.java:42)
- 首先,检查是不是路径有问题,如果你使用的是idea,看一下Working directory的路径
- 如果路径没有问题,需要看一下JDK版本与javassist版本是否一致?
例如:我们本地使用的是jdk11,而我们用的是javassist的3.22.0-GA版本就不行,查看ClassPool源码我们发现
public class ClassPool {private static java.lang.reflect.Method definePackage = null;static {// 原因原来在这if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9)try {AccessController.doPrivileged(new PrivilegedExceptionAction(){public Object run() throws Exception{Class cl = Class.forName("java.lang.ClassLoader");definePackage = cl.getDeclaredMethod("definePackage",new Class[] { String.class, String.class, String.class,String.class, String.class, String.class,String.class, java.net.URL.class });return null;}});}catch (PrivilegedActionException pae) {throw new RuntimeException("cannot initialize ClassPool",pae.getException());}}
所以,我们要保证javassist的版本与jdk版本匹配。
其实,javassist提供了手动设置路径的方式
ClassPool pool = ClassPool.getDefault();// 设置类路径pool.appendClassPath("***/Fighting/src/main/java/");// 创建一个public类,假如类已经存在会覆盖原类CtClass ct = pool.makeClass("com.pch.javassist.GenClass");
同时,写入方法也支持路径指定
//这里会将这个创建的类对象编译为.class文件
cc.writeFile("**/Fighting/src/main/java/");
动态修改方法体
AOP就是使用这种技术,动态的在一个方法中插入代码,下面我们举一个例子
例如,我们有一个如下类
public class Point {private int x;private int y;public Point() {}public Point(int x, int y) {this.x = x;this.y = y;}public void move(int dx, int dy) {this.x += dx;this.y += dy;}
}
动态的在move方法体前后插入一些代码
private static void modifyMethod() {ClassPool pool = ClassPool.getDefault();try {CtClass ct = pool.getCtClass("com.pch.javassist.Point");CtMethod m = ct.getDeclaredMethod("move");m.insertBefore("{ System.out.print(\"dx:\"+$1); System.out.println(\"dy:\"+$2);}");m.insertAfter("{System.out.println(this.x); System.out.println(this.y);}");ct.writeFile();//通过反射调用方法,查看结果Class pc = ct.toClass();Method move = pc.getMethod("move", new Class[]{int.class, int.class});Constructor<?> con = pc.getConstructor(new Class[]{int.class, int.class});move.invoke(con.newInstance(1, 2), 1, 2);} catch (Exception e) {e.printStackTrace();}}
另外需要注意的是:上面的insertBefore() 和 setBody()中的语句,如果你是单行语句可以直接用双引号,但是有多行语句的情况下,你需要将多行语句用{}括起来。javassist只接受单个语句或用大括号括起来的语句块。
需要注意的是: 语句和代码块可以引用属性和方法。如果方法是用-g选项编译的(也可以在类文件中包含局部变量属性),它们也可以引用它们插入的方法的参数。否则,它们必须通过下文描述的特殊变量$0, $1, $2等访问方法的参数。访问方法声明的本地变量是不允许的,尽管允许代码块声明新的本地变量。然而,如果这些变量在指定的行号可以获得且目标方法使用-g选项编译,insertAt()运行语句和代码块访问本地变量。
Javassist不允许移除方法或属性,但是它允许修改其名称。Javassist也不允许添加一个额外的参数到现有的方法。反之,应该添加一个新的接受额外参数或者其它参数的方法。
javassist先介绍到这里,下一篇文章我们介绍一下APT框架
参考:
- https://www.cnblogs.com/rickiyang/p/11336268.html
- https://blog.csdn.net/ShuSheng0007/article/details/81269295
- https://www.jianshu.com/p/b0986a86cd62
Javassist框架研究相关推荐
- DeepMind用基于AI的元强化学习框架研究多巴胺在学习过程中的作用
内容来源:ATYUN AI平台 最近,AI已经应用到一系列视频游戏中,如Atari经典的Breakout和Pong.尽管这样的表现令人印象深刻,但人工智能仍然依靠数千小时的游戏时间来达到并超越人类玩家 ...
- 新一代深度学习框架研究
点击上方蓝字关注我们 新一代深度学习框架研究 于璠 华为技术有限公司,广东 深圳 518000 摘要:从人工智能的历史出发,简述深度学习发展历程以及目前的挑战,通过介绍新一代深度学习框架的特点, ...
- 基于GIS的省级高速公路路面管理系统框架研究
基于GIS的省级高速公路路面管理系统框架研究 摘 要 基于GIS的省级高速公路路面管理系统是采用路面管理技术.交通地理信息系统技术和HDM-4模型建立的针对包括沥青路面和水泥混凝土路面在内的高速公 ...
- 城市大脑标准体系与评价指标总体框架研究
来源:城市大脑全球标准研究组 根据城市大脑标准的前期研究和中国指挥与控制学会(CICC)<城市大脑建设规范>标准启动会的专家意见,形成城市大脑标准体系与评价指标总体框架(如图1所示),供各 ...
- 记我的CTS框架研究(3)
现在,基础框架里要做的事做完了,在runTest里根据测试类型找到对应测试接口,我们要做的是CTS测试,所以就回到CTS框架里CompatibilityTest了,附图理解: 6 Compatibil ...
- Blockchain -Corda框架研究一 cordapp-example 学习笔记
Corda是Blockchain企业框架之一. 相关链接:https://docs.corda.net/ 国内:https://cncorda.com/ 这周开始准备学习Corda, 首先先研究一下h ...
- (前端版)RuoYi(若依开源框架)研究第一天
前端版RouYi(开源框架)研究第一天 项目结构 ├── build // 构建相关 ├── bin // 执行脚本 ├── public // 公共文件 │ ├── favicon.ico // f ...
- 计算机硬件框架,计算机硬件知识体系的结构框架研究
摘要:本文从计算机硬件知识体系的特点出发,探讨了计算机硬件知识的教学目标,从整体和实际等方面对如何构建合理的计算机硬件知识体系提出了一些自己的看法. 关键词:计算机硬件 知识体系 结构框架 中图分类号 ...
- Prism框架研究(一)
从今天起开始写一个Prism框架的学习博客,今天是第一篇,所以从最基本的一些概念开始学习这个基于MVVM的框架的学习,首先看一下Prism代表什么,这里引用一下比较官方的英文解释来看一下:Prism ...
- DM8168 DVRRDK软件框架研究
Netra(DM8168)处理器是个多核处理器,每个核之间相互独立却又相互关联,如何高效简洁地利用每个核完成一套系统功能是非常关键的,RDK这套软件平台就是针对这种多核平台设计的一套多通道视频应用方案 ...
最新文章
- Visual Studio 2013编译Mozilla NPAPI 示例注意事项
- C# 移除数组中重复项
- 增长之前,请先做好业务底线
- 网址收藏 plc实现
- mysql环境变量的配置
- 15 Process State and O.S. Scheduling
- linux ss命令详解
- OpenCV读取网络摄像头视频并保存到本地
- PuTTY用户手册(一)
- Hspice中的测量语句(II)
- 人口下降是否会导致房价下跌?
- 数模国赛备赛(5)论文写作与提交注意事项
- 【CityEngine】城市模型构建概述(持续更新)
- 玩转oj之1003题(地球人口承载力估计)
- min-width、max-width属性中min-content、max-content的含义,css中minmax()用法、1fr单位的含义----使页面具有相应性的属性以及属性值
- 个人对东西方人开放的拙见。
- dolphinscheduler 补数据
- linux eclipse插件安装,Linux 下 EclipseME 插件的安装步骤
- PostgreSQL安装之后,打开pgAdmin4后,点击servers下方没有任何内容的情况
- 重复造轮子,对此你的看法