目录

javassist使用笔记

https://i.loli.net/2020/01/19/mD6pU4ogVSvk7CM.png

简介

  • 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对象是不会参与裁剪的。
1
2
3
4
5
CtClasss cc = ...;
    :
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // OK since the class is not frozen.
  • 在我们调用defrost()方法后,该CtClass对象就会被解冻了。

修改类文件

1
2
3
4
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();
  • 1、ClassPool pool = ClassPool.getDefault():返回一个ClassPool对象,该对象据搜索CtClass对象的路径为系统默认路径
  • 2、从ClassPool中获取一个key为test.Rectangle的CtClass对象
  • 3、修改该对象使其继承自test.Point对象
  • 4、保存该对象到一个class文件

创建新类

  • 创建一个新的class,我们必须要先获取ClassPool,然后调用makeClass创建一个新的CtClass
1
2
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");

创建新接口类

  • 与创建新类类似,通过ClassPool调用makeInterface()方法即可

类搜索路径

获取默认的ClassPool我们可以通过调用ClassPool.getDefault()方法获取与JVM虚拟机具有相同的 一些路径的类。

如果一个代码运行在web应用服务器,如JBoss、Tomcat,此时ClassPool对象就不能够通过默认的ClassPool去获取使用的classes,因为一个web服务器使用类多个类加载器和系统类加载器; 在这种情况下,必须将其他类路径注册到ClassPool中,例如:

1
pool.insertClassPath(new ClassClassPath(this.getClass()));
  • 上面代码注册this对象对应的类路径到ClassPool中,

我们也可以注册一个目录名称作为类的搜索路径,例如:

1
2
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");

我们还可以添加URL作为类搜索路径:

1
2
3
ClassPool pool = ClassPool.getDefault();
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);
  • 以上代码会搜索 “http://www.javassist.org:80/java/” 路径,该URL仅用于搜索属于org.javassist包的类。例如,要加载类org.javassist.test.Main,将从以下位置获取其类文件:
1
http://www.javassist.org:80/java/org/javassist/test/Main.class

此外,您可以直接将字节数组提供给ClassPool对象,并从该数组构造CtClass对象。为此,请使用ByteArrayClassPath。例如,

1
2
3
4
5
ClassPool cp = ClassPool.getDefault();
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));
CtClass cc = cp.get(name);

获得的CtClass对象表示由b指定的类文件定义的类。如果调用了get()并且给定get()的类名等于由name指定的类名,则ClassPool从给定的ByteArrayClassPath中读取类文件

如果您不知道类的标准名称,则可以在ClassPool中使用makeClass():

1
2
3
ClassPool cp = ClassPool.getDefault();
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

makeClass()返回从给定输入流构造的CtClass对象。 您可以使用makeClass()将类文件急切地馈送到ClassPool对象。 如果搜索路径包含较大的jar文件,则可能会提高性能。 由于ClassPool对象按需读取类文件,因此它可能会在整个jar文件中重复搜索每个类文件。 makeClass()可用于优化此搜索。 由makeClass()构造的CtClass保留在ClassPool对象中,并且永远不会再次读取类文件。

搜索非标准的类包

用户可以扩展类搜索路径。他们可以定义一个实现ClassPath接口的新类,并将该类的一个实例提供给ClassPool中的insertClassPath()。这允许将非标准资源包含在搜索路径中


ClassPool

如何创建新的ClassPool对象

ClassPool作为单利设计出来,如果我们想要创建一个新的,可以通过如下方式获取:

1
ClassPool cp = new ClassPool(true);

级联ClassPool

如果一段代码运行在Web 服务器中,可以通过创建多个ClassPool已级联的方式操作,例如:

1
2
3
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");
  • child通过insertClassPath扩展自己的类搜索路径
  • 如果调用child.get(),则子ClassPool首先将委托给父ClassPool。如果父ClassPool找不到类文件,则child ClassPool尝试在./classes目录下找到一个类文件。
  • 如果child.childFirstLookup为true,则子ClassPool会在委派给父ClassPool之前尝试查找类文件。例如:
1
2
3
4
ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.appendSystemPath();         
child.childFirstLookup = true;

Class loader

如果预先知道必须修改哪些类,则修改类最简单的方法如下: 1、调用ClassPool.get()获取CtClass对象 2、修改CtClass 3、调用writeFile()或者toBytecode()去获取修改后的class文件