javassist使用笔记
简介
- Javassist是用于处理Java字节码的类库。 Java字节码存储在称为类文件的二进制文件中。每个类文件包含一个Java类或接口。
知识点
Javassist.CtClass
- 类Javassist.CtClass是类文件的抽象表示。 CtClass(编译时类)对象是用于处理类文件的句柄。
ClassPool
- ClassPool对象是一个包含CtClass对象的容器,它按需读取用于构造CtClass对象的类文件,并记录构造的对象以响应以后的访问
- 从实现上来看,ClassPool对象是一个存储这CtClass的hash 表,其使用类的名称作为key,使用get()方法在ClassPool中搜索指定key的CtClass对象,如果该对象不存在,则该方法读取一个类文件去构造一个新的CtClass对象,然后记录该对象到表中并作为get()方法的返回值返回。
CtClass
- CtClass代表class,修改一个类,必须从一个ClassPool中通过get()方法获取一个CtClass对象
- CtClass对象包含一个来自ClassPool的可以被修改的对象
方法 | 说明 | 备注 |
---|---|---|
toBytecode() | 返回当前class对象的字节码 | 内容2 |
toClass() | 直接加载CtClass对象 | toClass()方法将请求当前线程上下文的类加载器加载该class字节码,如果返回java.lang.Class对象则代表加载成功 |
detach() | 将对应的CtClass从ClassPool树中移除 | 如果ClassPool操作大量的CtClass对象,此时的内存开销是非常大的,使用该方法有利于内存的利用,调用该方法后我们将不能在操作该CtClass对象的任何方法,如需操作,请重新调用ClassPool的get方法获取新的CtClass对象 |
setName() | 修改CtClass在ClassPool中的对应的key记录 |
冻结:
- 一旦我们调用了writeFile()、toClass()、toBycode()方法,Javassist将对冻结该CtClass对象,禁止后续对该CtClass对象的修复,这么做是为了限制开发者在将class加载后再次修改class内容 解冻:
- 一个被冻结的CtClass类也可以做解冻操作来进行后续的修改,操作如下
CtClass对象裁剪
裁剪 Prunes:
-
在对一个CtClass对象做冻结操作后,内部还遗留一些数据结构 (attribute_info structures),这些数据结构对于一个已冻结的类来说是多余的,此时我们可以通过设置ClassPool.doPruning=true,如此在冻结CtClass后,Javassist就会去移除掉那些多余的数据结构来减少内存的占用。
-
注意的一点:被prunes后的CtClass就不可再次解冻来
如果我想让某些CtClass对象不参与Javassist的裁剪操作,这个该怎么处理呢?
- 避免某些CtClass对象参与裁剪操作,我们可以在创建该CtClass对象后,调用stopPruning(True),如此虽然我们调用了ClassPool.doPruning=true,但调用了stopPruning(True)的CtClass对象是不会参与裁剪的。
|
|
- 在我们调用defrost()方法后,该CtClass对象就会被解冻了。
修改类文件
|
|
- 1、ClassPool pool = ClassPool.getDefault():返回一个ClassPool对象,该对象据搜索CtClass对象的路径为系统默认路径
- 2、从ClassPool中获取一个key为test.Rectangle的CtClass对象
- 3、修改该对象使其继承自test.Point对象
- 4、保存该对象到一个class文件
创建新类
- 创建一个新的class,我们必须要先获取ClassPool,然后调用makeClass创建一个新的CtClass
|
|
创建新接口类
- 与创建新类类似,通过ClassPool调用makeInterface()方法即可
类搜索路径
获取默认的ClassPool我们可以通过调用ClassPool.getDefault()方法获取与JVM虚拟机具有相同的 一些路径的类。
如果一个代码运行在web应用服务器,如JBoss、Tomcat,此时ClassPool对象就不能够通过默认的ClassPool去获取使用的classes,因为一个web服务器使用类多个类加载器和系统类加载器; 在这种情况下,必须将其他类路径注册到ClassPool中,例如:
|
|
- 上面代码注册this对象对应的类路径到ClassPool中,
我们也可以注册一个目录名称作为类的搜索路径,例如:
|
|
我们还可以添加URL作为类搜索路径:
|
|
- 以上代码会搜索 “http://www.javassist.org:80/java/” 路径,该URL仅用于搜索属于org.javassist包的类。例如,要加载类org.javassist.test.Main,将从以下位置获取其类文件:
|
|
此外,您可以直接将字节数组提供给ClassPool对象,并从该数组构造CtClass对象。为此,请使用ByteArrayClassPath。例如,
|
|
获得的CtClass对象表示由b指定的类文件定义的类。如果调用了get()并且给定get()的类名等于由name指定的类名,则ClassPool从给定的ByteArrayClassPath中读取类文件
如果您不知道类的标准名称,则可以在ClassPool中使用makeClass():
|
|
makeClass()返回从给定输入流构造的CtClass对象。 您可以使用makeClass()将类文件急切地馈送到ClassPool对象。 如果搜索路径包含较大的jar文件,则可能会提高性能。 由于ClassPool对象按需读取类文件,因此它可能会在整个jar文件中重复搜索每个类文件。 makeClass()可用于优化此搜索。 由makeClass()构造的CtClass保留在ClassPool对象中,并且永远不会再次读取类文件。
搜索非标准的类包
用户可以扩展类搜索路径。他们可以定义一个实现ClassPath接口的新类,并将该类的一个实例提供给ClassPool中的insertClassPath()。这允许将非标准资源包含在搜索路径中
ClassPool
如何创建新的ClassPool对象
ClassPool作为单利设计出来,如果我们想要创建一个新的,可以通过如下方式获取:
|
|
级联ClassPool
如果一段代码运行在Web 服务器中,可以通过创建多个ClassPool已级联的方式操作,例如:
|
|
- child通过insertClassPath扩展自己的类搜索路径
- 如果调用child.get(),则子ClassPool首先将委托给父ClassPool。如果父ClassPool找不到类文件,则child ClassPool尝试在./classes目录下找到一个类文件。
- 如果child.childFirstLookup为true,则子ClassPool会在委派给父ClassPool之前尝试查找类文件。例如:
|
|
Class loader
如果预先知道必须修改哪些类,则修改类最简单的方法如下: 1、调用ClassPool.get()获取CtClass对象 2、修改CtClass 3、调用writeFile()或者toBytecode()去获取修改后的class文件