1、简介
Javassist(JAVA programming ASSISTant)是在Java中编辑字节码的类库;它使Java程序能够在运行时定义一个新类,并在JVM加载是修改类文件。
我们常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,他就是Javassist。
与其他类似的字节码编辑器不同,Javassist提供了两个级别的API:源级别和字节码级别。如果用户使用源级API,他们可以编辑类文件,而不知道Java字节码的规格。整个API只用Java语言的词汇来设计。您甚至可以以源文本的形式指定插入的字节码;Javassist在运行中编译它。另一方面,字节码级API允许用户直接编辑类文件作为其他编辑器。
在Javassist中,进行类表述的基本单元是CtClass(即“编译时的类”,compile time class)。组成程序的这些类会存储在一个ClassPool中,它本质上就是CtClass实例的一个容器。
ClassPool的实现使用了一个HashMap,其中key是类的名称,而value是对应的CtClass对象。
正常的Java类都会包含域、构造器以及方法。在CtClass中,分别与之对应的是CtField、CtConstructor和CtMethod。要定位某个CtClass,我们可以根据名称从ClassPool中获取,然后通过CtClass得到任意的方法,并做出我们的修改。如下所示:
Javassist提供的javassist.util.HotSwapper(3.1之前则是javassist.tools.HotSwapper,BTrace也是使用HotSwapper机制)类能够更加方便的动态重新加载类
虽然Javassist能够提供动态重新加载类的功能,不过由于它要求启用JPDA
作者:阿术和薇薇安
链接:https://www.jianshu.com/p/27337d7ca4c7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2、读取和写入字节码
类Javassist.CtClass是类文件的抽象表示形式。CtClass(编译时类)对象是处理类文件的句柄。下面的程序是一个非常简单的示例:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();
该程序首先获得一个ClassPool对象,它通过Javassist对象字节码修改。ClassPool对象是表示类文件的CtClass对象的容器。它根据需要读取类文件以构造CtClass对象,并记录构造对象以响应以后的访问。若要修改类的定义,用户必须首先从ClassPool对象获取对表示该类的CtClass对象的引用。ClassPool对象获得的,它被分配给一个变量cc。getDefault返回的ClassPool对象搜索默认的系统搜索路径。
可以修改从ClassPool对象获得CtClass对象(稍后将介绍如何修改CtClass的详细信息)。在上面的例子中,它被修改以便测试的超类。将矩形更改为类测试点。当最终调用CtClass()中的writeFile()时,此更改将反映在原始类文件中。
writeFile()时,此更改将反映在原始类文件中。
writeFile()将CtClass对象转换为类文件,并将其写入本地磁盘。Javassist还提供了一种直接获取修改后的字节码的方法。要获取字节码,请调用toBytecode():
byte[] b = cc.toBytecode();
您还可以直接加载CtClass:
Class clazz = cc.toClass();
toClass()请求当前线程的上下文类加载程序加载由CtClass表示的类文件。它返回一个表示已加载类的java.lang.Class对象。
2.1、定义类
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeCLass("Point");
此程序定义一个类Point,包括没有成员。可以使用CtNewMethod中声明的工厂方法创建点的成员方法,并在CtClass中追加到点与addMethod()。
makeClass()无法创新接口;可以使用 makeInterface () 做。接口中的成员方法可以在 CtNewMethod 中使用 abstractMethod () 创建。请注意, 接口方法是一种抽象方法。
2.2、冻结类
如果CtClass对象由writeFile()、toClass()或toBytecode()转换为类文件,Javassist将冻结该CtClass对象。那CtClass对象的进一步修改不被允许。这是为了在开发人员试图修改已加载的类文件时发出警告,因为JVM不允许重新加载类。
冻结的CtClass可以解冻,一遍允许对类定义进行修改。例如,
CtClass cc = ...;
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // OK since the class is not frozen.
2.3、类搜索路径
静态方法ClassPool.getDefault()返回的默认ClassPool将搜索底层JVM(Java虚拟机)具有的同一路径。如果某个程序在web应用程序服务器(如JBoss和Tomcat)上运行,则ClassPool对象可能无法找到用户类,因为这样的web应用程序服务器使用多个类加载器以及系统类加载程序。在这种情况下,必须将附加的类路径注册到ClassPool。假设池引用的是ClassPool对象:
pool.insertClassPath(new ClassClassPath(this.getClass()));
此语句注册用于加载次饮用的对象的类的类路径。可以将任何类对象用作参数而不是this.getClass()。用于加载由该类对象表示的类的类路径已注册。可以将目录名注册为类搜索路径。例如,下面的代码将目录/usr/local/javalib添加到搜索路径中:
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath("/usr/local/javalib");
用户可以添加的搜索路径不仅是一个目录,而且可以是一个URL:
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仅用于搜索属于包组织javassist的类。例如,要加载类org.javassist.test.Main,将从以下内容获取其类文件:
http://www.javassist.org:80/java/org/javassist/test/Main.class
此外,您可以直接给ClassPool对象一个字节数组,并从该数组构造一个CtClass对象。为此,请使用ByteArrayClassPath.例如:
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指定的类文件定义的类。ClassPool从给定的ByteArrayClassPath读取类文件(如果调用了get(),并且给定的类名为get()等于名称指定的类别。
如果您不知道该类的完全限定名,则可以在ClassPool中使用makeClass():
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对象中,不再读取类文件。
3、小结
本文简要介绍了javaassist及其简单用法。会有一些读者好奇:它和AOP有什么关系和区别?举个简单的例子即可:CGLib是动态代理的经典类库,其底层实现使用ASM,javaassist是类似ASM的东东。