读、写字节码

Javassist是一个处理字节码的类库。Java字节码存储在一个叫做*.class的二进制文件中。每个class文件包含一个java类或者接口。

javassist.CtClass代表一个class文件的抽象类表示形式。一个CtClass(compile-time class编译时的类)是一个处理class文件的句柄,以下是一个简单的程序:

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();

这段程序首先包含一个ClassPool对象,通过javassist控制字节码的修改。ClassPool对象是代表class文件的CtClass对象的容器。它根据构造一个CtClass对象的需求读取一个class文件,并记录被构建好的对象以供将来进行访问。
为了修改一个类的定义,用户必须首先从ClassPool对象的.get(className)方法获取一个CtClass引用。
在上述示例中,CtClass对象表示ClassPool中的类test.Rectangle,并且将其分配给变量cc
ClassPool对象由静态方法getDefault方法查找默认的系统检索path返回。

从实现上来看,ClassPool是一个CtClass的哈希表,使用class name作为key。

ClassPool.get()方法通过检索这个哈希表找到一个CtClass对象关联指定的key。

如果CtClass对象没有找到,get()方法会读取class文件去构造一个CtClass对象,记录在哈希表中然后作为get()的返回值返回。

ClassPool中获取到的CtClass对象是可以被修改的。在上述示例中,它被修改了, test.Rectangle的父类变更为test.Point,这个修改将会在最后CtClass.writeFile()方法调用后反映在class文件中。

writeFile()方法将CtClass对象转换到class文件并且将其写入本地磁盘。Javassist也提供了一个方法用于直接获取修改后的字节码:toBytecode():

byte[] b = cc.toBytecode();

也可以像这样直接加载CtClass:

Class clazz = cc.toClass();

toClass请求当前线程的上下文类加载器去加载class文件,返回一个java.lang.Class对象。

示例:

package org.byron4j.cookbook.javaagent;import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;import java.io.IOException;public class Hello {public void sayHello(){System.out.println("Hello!");}public static void main(String[] args){// 获取ClassPoolClassPool pool = ClassPool.getDefault();CtClass cc = null;try {// 通过ClassPool获取CtClasscc = pool.get("org.byron4j.cookbook.javaagent.Rectangle");// 设置父类cc.setSuperclass(pool.get("org.byron4j.cookbook.javaagent.Point"));// 更新到class文件中(仅在JVM中)cc.writeFile();// 获取修改后的字节码byte[] b = cc.toBytecode();System.out.println(new String(b));// 加载类(请求当前线程的上下文加载器加载CtClass代表的类)Class clazz = cc.toClass();System.out.println("superClass is :" + clazz.getSuperclass());} catch (NotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();}}
}

定义一个新的class

重新定义一个新的类,ClassPool.makeClass方法将会被调用:

// 定义一个新的类
ClassPool pool1 = ClassPool.getDefault();
CtClass cc2 = pool1.makeClass("hello.make.Point");
System.out.println(cc2.toClass()); // 输出class hello.make.Point

这个程序定义了一个Point类,未包含任何成员,成员方法可以通过使用CtClassaddMethod()方法传入一个CtMethod的工厂方法创建的对象作为参数来追加。

// 定义一个新的类
ClassPool pool1 = ClassPool.getDefault();
CtClass cc2 = pool1.makeClass("hello.make.Point");
//System.out.println(cc2.toClass().getMethods().length); // 9// 追加方法
cc2.addMethod(CtMethod.make("public void sayHello(){\n" +"        System.out.println(\"Hello!\");\n" +"    }",cc2));
System.out.println(cc2.toClass().getMethods().length);  // 10

makeClass()方法不能创建一个新的接口,需要使用makeInterface()方法才可以。
接口中的成员方法可以通过CtMethodabstractMethod方法创建。

冻结类Frozen class

冻结类的含义

如果一个CtClass对象通过writeFile()doBytecodetoClass方法被转换到class文件中,javassist则会冻结这个CtClass对象。再对这个CtClass对象进行操作则会不允许,这在开发者他们尝试去修改一个已经被JVM加载过的class文件的时候会发出警告,因为JVM不允许重加载一个class。

一个冻结的CtClass可以通过其defrost()方法解冻,解冻后可以允许对这个CtClass修改:

// 被冻结了,不能再修改(Exception in thread "main" java.lang.RuntimeException: hello.make.Point class is frozen)
// 解冻后可以修改
cc2.toBytecode();// 被冻结
cc2.defrost();// 解冻
System.out.println(cc2.getFields().length);
cc2.addField(CtField.make("private String name;", cc2));// 解冻后允许修改
cc2.writeFile();
System.out.println(cc2.getFields().length);

修剪prune类

如果CtClass.prune()方法被调用,则Javassist会在CtClass被冻结的时候(调用writeFile()doBytecodetoClass方法的时候)会修剪CtClass对象的数据结构。
为了降低内存消耗,修剪时会放弃对象中的不必要的属性。当一个CtClass对象被修剪后,方法的字节码则不能被访问除了方法名称、方法签名和注解。修剪过的CtClass对象不会被解冻。默认修剪值是false。

// 修剪ctClass
cc2.prune();// 设置修剪伪true
cc2.writeFile();// 冻结的时候,会进行修剪
System.out.println(cc2);//修剪后不能访问方法

禁止修剪stopPruning(true),必须在对象的前面调用:

CtClasss cc = ...;
cc.stopPruning(true);// 前面调用禁止修剪:
cc.writeFile();

注意:

当debugging的时候,你可能想临时禁止修剪、冻结和修改一个class文件到磁盘中,那么debugWriteFile是一个简便的方法。该方法禁止修剪、写入class文件、解冻、禁止重新开启修剪(如果开始是打开的话)。

类查找路径的设置Class search path

默认的ClassPool.getDefault()检索路径和JVM底层路径一致(classpath)。如果一个程序运行在一个web应用程序比如JBoss、Tomcat中,ClassPool对象则可能搜索步到用户的类,因为web应用使用了多个类加载器。在这种情况下,一个额外的classpath必须注册到ClassPool中。假设pool引用了一个ClassPool对象:

//添加class查找路径
pool1.insertClassPath(new ClassClassPath(this.getClass()));

这条语句注册了this引用对象加载的类的class path。你可以使用任意的Class对象作为参数。Class对象已经被注册上了的表现就是它所在的class path被加载了。

你也可以注册一个目录的名称作为一个class查找路径。例如,以下代码添加了/usr/local/javalib到class查找路径中:

// 添加文件目录作为calss查找路径
pool1.insertClassPath("/usr/local/javalib");

你还可以添加URL作为class查找路径:

// 添加URL作为class查找路径,第三个参数必须/开头、第四个参数必须.结尾
// 添加 "http://www.javassist.org:80/java/"
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool1.insertClassPath(cp);

这个程序添加了http://www.javassist.org:80/java/到class查找路径中。这个URL仅仅用来查找org.javassist.包的类。例如:加载一个org.javassist.test.Main类,它的class文件是:
http://www.javassist.org:80/java/org/javassist/test/Main.class
此外,你还可以直接给一个byte数组去构建一个CtClass对象,可以使用ByteArrayClassPath

// byte数组形式class path
ClassPool pool2 = ClassPool.getDefault();
byte[] arr = "org.byron4j".getBytes();
String name = "org.byron4j.Hello";
pool2.insertClassPath(new ByteArrayClassPath(name, arr));
CtClass ctClass = pool2.get(name);

CtClass对象ctClass表示字节数组b指定的class文件定义的类实例。ClassPool从给定的ByteArrayClassPath读取一个class文件.
如果你不确定类的完全限定名,你可以使用ClassPoolmakeClass方法:

// makeClass
ClassPool pool3 = ClassPool.getDefault();
InputStream ins = new FileInputStream("/usr/local/javalib");
CtClass ctClass1 = pool3.makeClass(ins);

makeClass返回一个从给定输入流中构造的CtClass对象。

你可以使用makeClass急切地将一个类文件给ClassPool对象,可以提升性能如果class查找路径包含大量的jar文件的话。因为ClassPool对象根据需要读取一个class文件,它可能重复为了每个class文件检索全部的jar文件,makeClass()则可用于优化该搜索方式。通过makeClass构造出来的CtClass对象会在ClassPool中保持存在,且不会再次去读取class文件了。

用户可以扩展class查找路径。可以定义一个新的类实现ClassPath接口,这种方式可以允许将非标准资源包含到class查找路径中。

javassist编程指南==读、写字节码相关推荐

  1. javassist编程指南(一)

    javassist编程指南(主译) javassist是什么? Javassist(Java 编程辅助)使得Java字节码操作更简单. Javassist可用于编辑字节码的类库. 允许Java程序可以 ...

  2. java自带的字节码技术_读懂字节码-还原JAVA源码

    已知有两个类: public class Father extends GrandFather { public String name = "father"; public vo ...

  3. java符号%3e%3e是什么意思,终于找到了!有了它你就可以读懂字节码了!

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 0x80 ior 将栈顶两int型数值作"按位或"并将结果压入栈顶 0x81 lor 将栈顶两long型数值作"按位或&qu ...

  4. java字节码提取if语句_终于找到了!有了它你就可以读懂字节码了!

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 0x80 ior 将栈顶两int型数值作"按位或"并将结果压入栈顶 0x81 lor 将栈顶两long型数值作"按位或&qu ...

  5. 0x30 java_终于找到了!有了它你就可以读懂字节码了!

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 0x80 ior 将栈顶两int型数值作"按位或"并将结果压入栈顶 0x81 lor 将栈顶两long型数值作"按位或&qu ...

  6. 字节码编程 | 使用Javassist动态生成Hello World

    大家好,我是冰河~~ 字节码编程在实际的业务开发(CRUD)中并不常用,但是随着网络编程,RPC.动态字节码增强技术和自动化测试以及零侵入APM监控的不断发展与大量使用,越来越多的技术需要使用到字节码 ...

  7. 字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 通过前面两篇 javassist 的基本内容,大体介绍了:类池(ClassPool) ...

  8. 字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》

    作者:小傅哥 博客:https://bugstack.cn - 汇总系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 案例是剥去外衣包装展示出核心功能的最佳学习方式! 就像是我们研 ...

  9. ASM字节码编程 | 用字节码增强技术给所有方法加上TryCatch捕获异常并输出

    作者:小傅哥 博客:https://bugstack.cn Wiki:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有所收 ...

最新文章

  1. 在CentOS 6.6 64bit上编译安装LLVM3.7,Clang,Libc++和libc++abi
  2. 读书笔记——《黑客大曝光》(1/8)
  3. Django博客系统工程创建和配置
  4. Python 之 matplotlib (三)坐标轴
  5. RAC安装时需要执行4个脚本及意义
  6. PyQt5 图形界面 - Qt Designer设置简体中文方法演示,Qt Designer字体设置,Qt Designer工具单独安装包获取,Qt Designer简体中文语言包获取
  7. mysql大表数据抽取_从云数据迁移服务看MySQL大表抽取模式
  8. Java线程与Android线程,Android线程篇(三):深入理解Java线程池(一)
  9. Java Web下访问外部jar,实例后的Object类型转化的问题
  10. 数据库表里面加表中的数据
  11. Python基于opencv调用摄像头获取个人图片
  12. Consistent hashing
  13. word到处html分页,请问如何把做好的word分页导出为html格式
  14. linux中脚本都是.sh吗,Linux跑脚本用sh和./有什么区别?
  15. 基于c扩展框架php,深入Php底层,用c为php编写拓展
  16. TC358775XBG是一颗将MIPI DSI信号转换成single/ dual -link LVDS的芯片,最高分辨率支持到1920x1200
  17. 生成一维码条码码,扫码查询相关条码信息
  18. UIUC说对抗样本出门会失效,被OpenAI怼回来了!
  19. Vue之组件与组件通信
  20. android全屏模式动画,Android--Toast全屏和动画(模拟QQ样式)

热门文章

  1. JAVA旅游(工厂方法模式)
  2. DD-WRT 中继桥接模式 配置方法
  3. 设计原则-单一职责原则
  4. 特斯拉AI总监的MNIST训练之旅
  5. 【uip移植】在AVR单片机ATMega16A上运行uip协议栈,网卡使用ENC28J60
  6. ECSHOP v2.5数据库字典
  7. LeNet-5网络结构及训练参数计算
  8. 深度学习之基于CNN和VGG19实现猫狗大战
  9. 一种递归式的非零自然数全分解方法
  10. ovirt guest agent 安装