Javassist概述

Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件,添加新的方法,或者是修改已有的方法。Javassist使用户不必关心字节码相关的规范也是可以编辑类文件的。

Java常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,它就是Javassit。

核心API

Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在ClassPool中(ClassPool是一个存储CtClass对象的容器)。

CtClass中的CtFieldCtMethod分别对应Java中的字段和方法。通过CtClass对象即可对类新增字段和修改方法等操作了。

ClassPool

ClassPool是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。

  • getDefault:返回默认ClassPath 下的 ClassPool是 单例模式,一般通过该方法创建我们的ClassPool;
  • appendClassPath, insertClassPath:将一个 ClassPath 加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬。
  • toClass将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class。
  • get,getCtClass:根据类路径名获取该类的CtClass对象。
  • makeClass:创建新类。
  • makeInterface :创建新接口。接口中的成员方法可以在 CtNewMethod 中使用 abstractMethod () 创建。

需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API中给出的解决方案是 有意识的调用 CtClass 的 detach() 方法以释放内存。

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();//插入ClassPath加到类搜索路径ClassLoader cl = Thread.currentThread().getContextClassLoader();pool.insertClassPath(new LoaderClassPath(cl));try {//获取指定类CtClassCtClass ctClass = pool.get("com.ymqx.动态增加属性和注解.TargetBean");} catch (NotFoundException e) {e.printStackTrace();}//创建新类CtClass newCtClass = pool.makeClass("com.ymqx.动态增加属性和注解.DestBean");//将修改后的CtClass加载至当前线程的上下文类加载器中try {pool.toClass(newCtClass);} catch (CannotCompileException e) {e.printStackTrace();}}
}

静态方法 ClassPool.getDefault () 返回的默认 ClassPool 将搜索底层 JVM (Java 虚拟机) 具有的同一路径。如果某个程序在 web 应用程序服务器 (如 JBoss 和 Tomcat) 上运行, 则 ClassPool 对象可能无法找到用户类, 因为这样的 web 应用程序服务器使用多个类加载器以及系统类加载程序。在这种情况下, 必须将附加的类路径注册到 ClassPool。

CtClass

CtClass 是类文件的抽象表示形式,CtClass 对象是处理类文件的句柄。

  • isFrozen:判断一个类是否已被冻结。
  • isModified:如果类被修改过的话就返回true,否则返回false。
  • isPrimitive:是否为Java原始类型:boolean, byte, char, short, int, long, float, double, or void。
  • prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用。
  • freeze:冻结一个类,使其不可修改。
  • defrost:解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法。
  • detach: 将该class从ClassPool中删除。
  • getName:获取CtClass代表的类的全限定类名。
  • setName:修改类名,必须以全限定类名的形式传递参数。
  • isInterfaceisAnnotationisEnum:CtClass代表的类对象是否为接口、注解、枚举类型。
  • getFieldgetMethodgetConstructor:获取类的字段、方法、构造函数。
  • getFieldsgetMethodsgetConstructors:获取类的字段、方法、构造函数的集合。
  • addFieldaddMethodaddConstructor:类中添加字段、方法、构造函数。
  • removeFieldremoveMethodremoveConstructor:从类中移除字段、方法、构造函数。
  • insturment:向类中注册CodeConverter或者ExprEditor,其中CodeConverter用于代码的转换,ExprEditor用于编辑方法体中满足条件的代码块,instrument(ExprEditor)是CtMethod和CtConstructor调用的方法。
  • toClasstoBytecodewriteFile:将CtClass对象转换为Class对象、bytecode字节码、写入.class文件中,调用这些方法后,CtClass对象将会被冻结,无法进行修改。
  • setSuperclass:设置父类。

CtField

CtField代表类的字段,既可以通过CtClass获取类中已经存在的字段,也可以新建一个CtField对象并添加到CtClass中。

  • make(String src, CtClass declaring):通过源码的形式构造CtField对象,例如src可以为:“public String name;”,不要忘记";",否则会编译失败,declaring代表我们要添加字段的类
  • getName:获取字段名
  • setName:修改字段名
  • getModifierssetModifiers:获取访问修饰符、设置访问修饰符,其中,设置访问修饰符可以通过Modifier中提供的常量字段设置,并且可以组合设置,例如要设置public static,可以使用如下设置:Modifier.PUBLIC | Modifier.STATIC
  • getAnnotationgetAnnotations:获取字段上的注解
  • getSignature:返回代表字段类型的字符串,如果是String类型的话,返回结果为:Ljava/lang/String;
  • getGenericSignaturesetGenericSignature:获取和设置字段的泛型类型
  • getConstantValue:只能获取Java基本类型的包装类型对应的常量值,如果是其他类型的常量的话则会返回null
  • getFieldInfo:获取字段信息,用于获取、修改、新增注解

在向CtClass类中添加新字段的时候,可以添加字段的初始值,在调用CtClass.addField方法时,向其中传入CtField.Initializer参数即可设置字段对应的初始值,例如:

CtField username = new CtField(pool.get("java.lang.String"), "username", ctClass);
username.setModifiers(Modifier.PRIVATE);ctClass.addField(username, CtField.Initializer.constant("ZhangSan"));

示例1:创建新类,添加一个int类型的,名字为value的变量,以及getter/setter

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");//添加一个int类型的,名字为value的变量,以及getter/settertry {CtField ctField = new CtField(CtClass.intType,"value",ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);//使用CtNewMethod添加getter/setterCtMethod getter = CtNewMethod.getter("getValue", ctField);CtMethod setter = CtNewMethod.setter("setValue", ctField);ctClass.addMethod(getter);ctClass.addMethod(setter);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行会生成新的.class文件

package com.ymqx.动态增加属性和注解;public class CreateBean {private int value;public int getValue() {return this.value;}public void setValue(int var1) {this.value = var1;}public CreateBean() {}
}

CtConstructor

CtConstructor代表类的构造函数,也可能代表类的初始化函数;可以通过isClassInitializer()方法确定是否为类初始化函数。

  • getLongName:获取带有参数的名称(全限定类名和构造函数参数类型),如:javassist.CtConstructor(CtClass[],CtClass)
  • getName:只获取构造函数名也就是类名
  • isConstructor:是否为构造函数
  • isClassInitializer:是否为类的初始化函数
  • callSuper:是否调用父类构造函数
  • setBody:设置构造函数源码
  • insertBeforeBody:在构造函数开头插入代码
  • setModifiers:设置访问权限

通过上述方法,我们可以自定义构造函数并添加到CtClass对象中去,也可以访问现有构造函数的相关信息。如果需要创建CtConstructor对象,可以通过 CtNewConstructor

  • make:构造CtNewConsturctor对象
  • copy:利用原有的CtConstructor对象复制一个新的CtConstructor对象
  • defaultConstructor:创建默认构造函数
  • skeleton:创建一个带有参数的构造函数,但是该函数体只调用super()方法,其余代码需要自己编写插入
// 方法一,通过new CtConstructor()的方式创建CtConstructor对象
// new CtClass[]{}相当于构造函数的参数,ctClass为要添加构造函数的类对象
CtConstructor none = new CtConstructor(new CtClass[] {}, ctClass);
none.setBody("System.out.println(\"使用javassist产生的默认构造函数\");");
ctClass.addConstructor(none);// 方法二,通过CtNewConstructor提供的静态方法创建CtConstructor对象
CtNewConstructor.make("public User() {System.out.println(\"使用javassist产生的默认构造函数\");}", ctClass);

CtMethod

CtMthod代表类中的某个方法,可以通过CtClass提供的API获取或者CtNewMethod新建,通过CtMethod对象可以实现对方法的修改。

  • make:通过源码的形式创建CtMethod对象
  • getName:获取函数名
  • setName:设置函数名(可以用于修改函数名称)
  • setBody:设置函数体
  • setWrappedBody:通过提供的CtMethod对象的函数体来设置当前CtMethod对象的函数体
  • setModifiers:设置函数的访问修饰符
  • insertBeforeinsertAfterinsertAt:在函数最开始 | 结束位置 | 任意位置(需要知道代码行号)插入代码
  • addLocalVariable:添加局部变量
  • addCatch:添加try catch语句

编译的时候,会把变量名抹掉,传递的参数会依次在局部变量表中的顺序。
比如,如果方法体是 public void test(int a,int b,int c){ … } ,那么a,b,c就对应本地变量表中的1,2,3的位置。Javassist中获取变量时就不能使用原始的名字,参数使用的是$1,$2,$3。

还可以通过 CtNewMethod生成方法

  • make:通过源码构造CtMethod对象
  • copy:利用原有的CtMethod对象复制一个新的CtMethod对象
  • abstractMethod:接口方法只能通过CtNewMethod.abstractMethod来创建抽象方法
  • getter:生成getter方法
  • setter:生成setter方法

示例2:创建新类,添加一个hello的方法

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");//添加一个hello的方法try {CtMethod ctMethod = new CtMethod(CtClass.intType, "hello", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("return $1 + $2;");//在方法体的前后分别插入代码ctMethod.insertBefore("System.out.println(\"在前面插入了:\" + $1);");ctMethod.insertAfter("System.out.println(\"在最后插入了:\" + $1);");ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行会生成新的.class文件

package com.ymqx.动态增加属性和注解;public class CreateBean {public int hello(int var1, double var2) {System.out.println("在前面插入了:" + var1);double var5 = (double)var1 + var2;System.out.println("在最后插入了:" + var1);return (int)var5;}public CreateBean() {}
}

示例3:创建新类,使用CtNewMethod添加方法

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");try {//添加一个int类型的,名字为value的变量,以及getter/setterCtField ctField = new CtField(CtClass.intType,"value",ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);//使用CtNewMethod添加getter/setterCtMethod getter = CtNewMethod.getter("getValue", ctField);CtMethod setter = CtNewMethod.setter("setValue", ctField);ctClass.addMethod(getter);ctClass.addMethod(setter);//添加一个hello2的方法CtMethod ctMethod = CtNewMethod.make("public int hello2(int num1, double num2) {\n" +"    double sum = (double)num1 + num2;\n" +"    return (int)sum;\n" +"}", ctClass);ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

生成的 .class文件:

package com.ymqx.动态增加属性和注解;public class CreateBean {private int value;public int getValue() {return this.value;}public void setValue(int var1) {this.value = var1;}public int hello2(int var1, double var2) {double var4 = (double)var1 + var2;return (int)var4;}public CreateBean() {}
}

CtNewMethod.getter()和CtNewMethod.setter()更简洁生成getter/setter方法,make()方法生成的方法,变量名都会抹掉,替换成var1、var2等

Javassist中特殊参数示例

标识符 作用
$0 this
$1、$2、 … 方法参数(1-N是方法参数的顺序)
$args 方法参数数组,类型为Object[]
$$ 所有方法参数,例如:m($$)相当于m($1,$2,…)
$cflow(…) control flow,这是一个只读变量,返回指定方法递归调用的深度。
$r 返回结果的类型,在强制转换表达式中使用。
$w 包装器类型,在强制转换表达式中使用。
$_ 返回的结果值
$sig 类型为java.lang.Class的参数类型对象数组
$type 类型为java.lang.Class的返回值类型
$class 类型为java.lang.Class的正在修改的类

目前只用到简单方法,具体用法参考:
关于Java字节码编程javassist的详细介绍

ConstPool

创建属性相关实例时,比如,给字段属性增加注解等,都需要参数ConstPool常量池。

获取方式:

ClassPool pool = ClassPool.getDefault();//获取实体类
CtClass ctClass = pool.get("类名");
ConstPool constPool = ctClass.getClassFile().getConstPool();

Javassist基本用法相关推荐

  1. Javassist实战-修改现有类

    对于新增类应用场景不常见,而修改现有类应用场景更多,比如常见的日志切面,权限切面. 修改现有.class文件 已有类新增方法 1.现有类Person public class Person {priv ...

  2. 美团热修复Robust源码庖丁解牛(第一篇字节码插桩)

    如果你想对java编译后的class文件做一些手脚的话,市面上有供你选择的asm.javassist.aspectJ(aop面向切面编程)等等,一般修改class文件的用途有你想统计一些东西,例如ap ...

  3. proxy aspectj_使用AspectJ,Javassist和Java Proxy进行代码注入的实用介绍

    proxy aspectj 静态地或在运行时将代码片段注入已编译的类和方法中的功能可能会很有帮助. 这尤其适用于在没有源代码的第三方库中或在无法使用调试器或探查器的环境中对问题进行故障排除. 代码注入 ...

  4. 使用AspectJ,Javassist和Java Proxy进行代码注入的实用介绍

    静态地或在运行时将代码片段注入已编译的类和方法中的功能可能会很有帮助. 这尤其适用于在没有源代码的第三方库中或在无法使用调试器或探查器的环境中对问题进行故障排除. 代码注入对于处理涉及整个应用程序的问 ...

  5. Java字节码编程之非常好用的javassist

    我为什么要研究这个? 因为我在开发一个框架的时候需要用到. 我开发的这个框架,有一个注解,当用户输入变量名,类名的时候,我这个框架可以为其自动生成一个对象,并加载到内存中供以后使用. 这个小功能可费尽 ...

  6. Javassist 使用指南

    说明:翻译的太好,怕原文丢失就转载了. 1. 读写字节码 我们知道 Java 字节码以二进制的形式存储在 class 文件中,每一个 class 文件包含一个 Java 类或接口.Javaassist ...

  7. javassist使用中遇到的问题记录

    Javassit提供了运行时操作Java字节码的方法,其效率低于asm.javassist主要是提供了代码级别的修改(也有bytecode级别),相比与asm的字节码级别的修改,学习成本低,开发效率高 ...

  8. 牛散村:Java字节码编程javassist的详细介绍

    本篇文章将和大家分享一下关于Java字节码编程中一个非常之好用的技术javassist,下面将详细为大家介绍一下javassist技术,以及具体实例代码讲解. 一.Javassist入门 (一)Jav ...

  9. Javassist 官方文档 随手笔记

    Javassist 官方文档 随手笔记 Javassist.CtClass Class search path Introspection and customization \$0, \$1, \$ ...

  10. c语言中external,static关键字用法

    static用法: 在C中,static主要定义全局静态变量.定义局部静态变量.定义静态函数. 1.定义全局静态变量:在全局变量前面加上关键字static,该全局变量变成了全局静态变量.全局静态变量有 ...

最新文章

  1. chrome浏览器上传文件延迟_扫描识别工具Dynamic Web TWAIN使用教程:移动浏览器捕获(下)...
  2. java窗口绘图_JAVA-窗口中绘制图形
  3. What you need to know about AllowUnsafeUpdates (Part 1) [转载]
  4. 009_Redis的事物
  5. Android学习笔记(三):android画图之paint
  6. 在spring boot中三分钟上手apache顶级分布式链路追踪系统skywalking
  7. 公告:CSDN下载频道在线举报功能正式上线
  8. angular toastr插件
  9. python离散点切向量的计算与可视化
  10. C语言编写的学生考勤信息管理系统
  11. 解决Win10系统桌面图标异常问题(恢复步骤)
  12. Python绘制地理图--Cartopy基础
  13. 给ubuntu添加字体
  14. 职场寒冬来袭,“零工经济”让你比90%的人更有安全感
  15. cdr最新软件下载2023中文版电脑64位免费安装包
  16. unix编程书籍推荐
  17. “无实物尝百味”通过控制微电流刺激产生味觉—1.硬件设计篇
  18. html5动态画图,HTML5使用Canvas动态绘制心型线和玫瑰线的教程
  19. 数据分析都不会,你敢玩基金?Python爬取基金并进行对比!
  20. outlook 服务器未响应,出现错误,Outlook 无法设置你的帐户

热门文章

  1. 3D游戏编程4--打飞碟
  2. 时空召唤服务器维护2020,2020年11月24日定期维护解读
  3. 泛微协同商务系统办公自动化解决方案
  4. 北京大学软件与微电子学院嵌入式系统工程系
  5. 尼康单反AF自动对焦模式与AF区域模式详解
  6. win服务器接她tar.gz文件,Window平台下压缩文件为tar.gz格式的方法
  7. 日立服务器显示地址操作异常,日立电梯服务器地址操作异常
  8. fileupload 使用
  9. 数字逻辑实验一--组合逻辑电路的设计
  10. 尔雅 科学通史(吴国盛) 个人笔记及课后习题 2018 第七章 实验传统的兴起