Javasist实战

首先引入pom依赖:
org.javassist javassist 3.28.0-GA 复制代码
整个Javasist操作字节码有四个类是最为关键和核心的,其实如果你看一个类的组成(方法+属性),大概也就知道都有哪些东西:

类名 描述
Javassist.CtClass Javassist.CtClass就代表着一个class文件的抽象,一个CtClass对象就是操作一个class文件的句柄
ClassPool ClassPool可以看作是保存着CtClass对象的一个容器,当我们需要获取指定类对应的CtClass时,就可以通过指定类的全限定名去获取。要对字节码进行操作,第一步就是要从ClassPool中获取对应的CtClass。从开发视角看ClassPool是一个CtClass对象的哈希表,类名作为键,CtClass作为value
CtMethod 一个实例代表一个方法
CtField 一个实例代表一个属性

有了关键类之后,再来看下这些关键类(主要就看下ClassPool和CtClass)哪些关键API吧:

CtClass

标题
writeFile() translates the CtClass object into a class file and writes it on a local disk.
toBytecode() 获取字节码
toClass() 获取当前执行线程的contextClassLoader对该字节码文件进行加载
setName() 从名字上看是修改类名,但是这里其实是基于当前CtClass复制一个出来,所以这里set的是新类的名字
defrost() 如果一个CtClass对象被writeFile()、toClass()或toBytecode()转换成一个类文件,Javassist将冻结该CtClass对象。不允许进一步修改该CtClass对象。这是为了在开发人员试图修改已经加载的类文件时发出警告,因为JVM不允许重新加载类。那么我们可以通过此方法对该CtClass进行解冻,之后就可以修改了
detach() 需要注意的是ClassPool在运行期间,所有创建的CtClass都会永远保存。如果CtClass对象的数量变得惊人地大,ClassPool的这种规范可能会导致巨大的内存消耗(这种情况很少发生,因为Javassist试图以各种方式减少内存消耗)。为了避免这个问题,您可以显式地从ClassPool中删除一个不必要的CtClass对象。如果在一个CtClass对象上调用detach(),那么该CtClass对象将从ClassPool中删除。当然,除了这种方式去避免这种问题,还有另外一种方式:当前ClassPool直接不要了,当垃圾回收时,那么所有相关的CtClass也都没了

ClassPool

标题
getDefault() 通过getDefault()获取的ClassPool,其默认搜索路径是System Path
insertClassPath 假如我们的javasist运行在诸如Jboss,Tomcat这种自定义了自己加载器的实现,我们如果通过getDefault获取时,是无法获取到一些路径下的类的,此时我们可以通过该方法去指定一些搜索路径

从上面insertClassPath可以知道,是不是感觉和类加载有点像呢,说白了就是找class文件嘛,那怎么找?类加载机制提供了双亲委派,然而javasist也提供了类似的机制,我们可以给ClassPool指定父ClassPool,这样,每当我们调用get()时,都会优先从父ClassPool中去寻找,就像这样:
ClassPool parent = ClassPool.getDefault(); ClassPool child = new ClassPool(parent); child.insertClassPath(“./classes”); 复制代码
然后javasist除了这,还支持从儿子往爷爷头上找,就是自己先找,自己找不到,再问自己爹找,这个可以通过属性childFirstLookup去控制。

代码实战

说了这么多,接下来直接上代码

通过Javasist修改已有类的行为

这里先定义一个我们目标要进行修改的类:
public class TargetObject { public void sayHello(){ System.out.println(“hello”); } } 复制代码
接下来我将通过javasist改变sayHello的行为,就像是我们平常使用的AOP一样:
ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.get(“com.cf.study.DailyStudy.javasisttest.TargetObject”); CtMethod method = ctClass.getDeclaredMethod(“sayHello”); String beforeExecute = “System.out.println(“before…”);”; String afterExecute = “System.out.println(“after…”);”; method.insertBefore(beforeExecute); method.insertAfter(afterExecute); TargetObject targetObjectAgent = (TargetObject)ctClass.toClass().getConstructor().newInstance(); targetObjectAgent.sayHello(); 复制代码
输出:
before… hello after…