Javassist 介绍 Javassist(Java 编程助手)使 Java 字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库。它使 Java 程序可以在运行时定义新类,并在 JVM 加载它时修改类文件。与其他类似的字节码编辑器不同,Javassist 提供了两个级别的 API:源级别和字节代码级别。如果用户使用源代码级 API,则他们可以在不了解 Java 字节码规范的情况下编辑类文件。整个 API 仅使用 Java 语言的词汇表进行设计。甚至可以以源文本的形式指定插入的字节码。Javassist 可以即时对其进行编译。另一方面,字节码级别的 API 允许用户像其他编辑器一样直接编辑类文件
Javassist 是用于编辑(创建,修改).class字节码文件的 Java 库,一般情况下我们都是在.java文件中写代码,然后编译成.class文件,在加载进 Java 虚拟机中执行代码,如果要修改已编译好的文件,要使用010 Editor去手动的计算一些偏移值进行修改,但是 Javassist 的出现,使得我们操作.class文件变得简单,并且可以在 Java 虚拟机 JVM 运行时动态地改变.class文件
依赖
<dependencies > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.27.0-GA</version > </dependency > </dependencies >
基本概念 Javassist 是一个开源的分析、编辑和创建Java字节码的类库. 其主要优点在于简单快速. 直接使用 java 编码的形式, 而不需要了解虚拟机指令, 就能动态改变类的结构, 或者动态生成类. Javassist中最为重要的是ClassPool,CtClass, CtMethod以及CtField这几个类.
ClassPool: 一个基于Hashtable实现的CtClass对象容器, 其中键是类名称, 值是表示该类的CtClass对象
CtClass: CtClass表示类, 一个CtClass(编译时类)对象可以处理一个class文件, 这些CtClass对象可以从ClassPool获得
CtMethods: 表示类中的方法
CtFields: 表示类中的字段
使用示例:
import javassist.*; import java.io.IOException; public class Javasist_Learning { public static void Create_Person () throws NotFoundException, CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Javasist.learning.Person" ); CtField ctField1 = new CtField (pool.get("java.lang.String" ),"name" ,ctClass); ctField1.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField1,CtField.Initializer.constant("Feng" )); ctClass.addMethod(CtNewMethod.setter("setName" ,ctField1)); ctClass.addMethod(CtNewMethod.getter("getName" ,ctField1)); CtConstructor constructor = new CtConstructor (new CtClass []{},ctClass); constructor.setBody("{name = \"Feng\";}" ); ctClass.addConstructor(constructor); CtMethod ctMethod = new CtMethod (CtClass.voidType,"printName" ,new CtClass []{},ctClass); ctMethod.setModifiers(Modifier.PRIVATE); ctMethod.setBody("{System.out.println(name);}" ); ctClass.addMethod(ctMethod); ctClass.writeFile("C:\\Users\\34946\\Desktop\\安全学习\\ROME\\" ); } public static void main (String[] args) throws NotFoundException, CannotCompileException, IOException { Create_Person(); } }
ClassPool的相关方法
getDefault: 返回默认的ClassPool是单例模式的,一般通过该方法创建我们的ClassPool;
appendClassPath, insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;
toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;
get , getCtClass: 根据类路径名获取该类的CtClass对象,用于后续的编辑。ClassPool pool = new ClassPool (true );ClassPool pool1 = ClassPool.getDefault();
为减少ClassPool可能导致的内存消耗. 可以从ClassPool中删除不必要的CtClass对象. 或者每次创建新的ClassPool对象.ctClass.detach(); ClassPool pool2 = new ClassPool (true );
CtClass的相关方法
freeze: 冻结一个类,使其不可修改;
isFrozen : 判断一个类是否已被冻结;
prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;
detach : 将该class从ClassPool中删除;
writeFile : 根据CtClass生成 .class 文件;
toClass : 通过类加载器加载该CtClass。
setInterfaces: 添加父接口
setSuperclass: 添加父类
常用方法 void setSuperclass (CtClass clazz) java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup) byte [] toBytecode() void writeFile () void writeFile (java.lang.String directoryName) CtConstructor makeClassInitializer ()
获取CtClass CtClass ctClass = pool.get("com.kawa.ssist.JustRun" );CtClass ctClass1 = pool.getOrNull("com.kawa.ssist.JustRun" );
创建CtClass CtClass ctClass2 = pool.getAndRename("com.kawa.ssist.JustRun" , "com.kawa.ssist.JustRunq" );CtClass ctClass3 = pool.makeClass("com.kawa.ssist.JustRuna" );CtClass ctClass4 = pool.makeClass(new FileInputStream (new File ("/home/un/test/JustRun.class" )));
CtClass基础信息 String simpleName = ctClass.getSimpleName();String name = ctClass.getName();String packageName = ctClass.getPackageName();CtClass[] interfaces = ctClass.getInterfaces(); CtClass superclass = ctClass.getSuperclass();CtMethod ctMethod = ctClass.getDeclaredMethod("getName()" , new CtClass [] {pool.get(String.class.getName()), pool.get(String.class.getName())});CtField ctField = ctClass.getField("name" );ctClass.isArray(); ctClass.isPrimitive(); ctClass.isInterface(); ctClass.isEnum(); ctClass.isAnnotation(); ctClass.freeze () ctClass.isFrozen() ctClass.prune() ctClass.defrost()
CtClass类操作 ctClass.addInterface(...); ctClass.addConstructor(...); ctClass.addField(...); ctClass.addMethod(...);
CtClass类编译 Class clazz = ctClass.toClass();ClassFile classFile = ctClass.getClassFile();byte [] bytes = ctClass.toBytecode();
CtMethod的相关方法 上面我们创建一个新的方法使用了CtMethod类。CtMthod代表类中的某个方法,可以通过CtClass提供的API获取或者CtNewMethod新建,通过CtMethod对象可以实现对方法的修改。
insertBefore : 在方法的起始位置插入代码;
insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
insertAt : 在指定的位置插入代码;
setBody: 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
make : 创建一个新的方法。
获取CtMethod属性 CtClass ctClass5 = pool.get(TestService.class.getName());CtMethod ctMethod = ctClass5.getDeclaredMethod("selectOrder" );String methodName = ctMethod.getName();CtClass returnType = ctMethod.getReturnType();ctMethod.getLongName(); ctMethod.getSignature(); List<String> argKeys = new ArrayList <>(); MethodInfo methodInfo = ctMethod.getMethodInfo();CodeAttribute codeAttribute = methodInfo.getCodeAttribute();LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);int len = ctMethod.getParameterTypes().length;int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1 ;for (int i = pos; i < len; i++) { argKeys.add(attr.variableName(i)); }
CtMethod方法体修改 ctMethod.insertBefore("" ); public void setBody (String src) ; ctMethod.insertAfter("" ); ctMethod.insertAt(10 , "" ); ctMethod.addParameter(CtClass); ctMethod.setName("newName" ); ctMethod.setBody("{$0.name = $1;}" ); ctMethod.make("kawa" ,CtClass);
异常块 addCatch() 在方法中加入try catch块, 需要注意的是, 必须在插入的代码中, 加入return值$e代表异常信息.插入的代码片段必须以throw或return语句结束
CtMethod m = ...;CtClass etype = ClassPool.getDefault().get("java.io.IOException" );m.addCatch("{ System.out.println($e); throw $e; }" , etype); try { } catch (java.io.IOException e) { System.out.println(e); throw e; }
CtConstructor CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数(类初始化器)。可以通过CtConstructor.make方法创建
常用方法 //设置构造函数主体。 void setBody(java.lang.String src) //从另一个构造函数复制一个构造函数主体。 void setBody(CtConstructor src, ClassMap map) //复制此构造函数并将其转换为方法。 CtMethod toMethod(java.lang.String name, CtClass declaring)
类搜索路径 通过ClassPool.getDefault()获取的ClassPool使用 JVM 的类搜索路径。如果程序运行在JBoss或者Tomcat等 Web 服务器上,ClassPool可能无法找到用户的类,因为 Web 服务器使用多个类加载器作为系统类加载器。在这种情况下,ClassPool 必须添加额外的类搜索路径。
ClassPool pool = ClassPool.getDefault();pool.insertClassPath("/usr/local/javalib" ) ClassPath cp = new URLClassPath ("www.javassist.org" , 80 , "/java/" , "org.javassist." );pool.insertClassPath(cp); byte [] b = a byte array;String name = class name ;cp.insertClassPath(new ByteArrayClassPath (name, b)); InputStream ins = an input stream for reading a class file ;CtClass cc = cp.makeClass(ins);
通过ClassClassPath添加搜索路径
pool.insertClassPath(new ClassClassPath (Person.getClass())); pool.insertClassPath(new ClassClassPath (this .getClass())); pool.appendClassPath(new ClassClassPath (this .getClass())); pool.insertClassPath("/xxx/lib" );
上面的语句将Person类添加到pool的类加载路径中。但在实践中,我发现通过这个可以将Person类所在的整个jar包添加到类加载路径中。通过ByteArrayPath添加搜索路径
ClassPool cp = ClassPool.getDefault();byte [] buf = 字节数组;String name = 类名;cp.insertClassPath(new ByteArrayClassPath (name, buf)); CtClass cc = cp.get(name);
示例中的 CtClass 对象是字节数据buf代表的class文件。将对应的类名传递给ClassPool的get()方法,就可以从字节数组中读取到对应的类文件。通过URL指定搜索路径
ClassPool pool = ClassPool.getDefault();ClassPath cp = new URLClassPath ("www.sample.com" , 80 , "/out/" , "com.test." );pool.insertClassPath(cp);
上述代码将http://www.sample.com:80/out添加到类搜索路径。并且这个URL只能搜索 com.test包里面的类。例如,为了加载 com.test.Person,它的类文件会从获取http://www.sample.com:80/out/com/test/Person.class获取 通过输入流加载class 如果你不知道类的全名,可以使用makeClass()方法:
ClassPool cp = ClassPool.getDefault();InputStream ins = class文件对应的输入流;CtClass cc = cp.makeClass(ins);
makeClass()返回从给定输入流构造的CtClass对象。你可以使用makeClass()将类文件提供给ClassPool对象。如果搜索路径包含大的jar文件,这可能会提高性能。由于ClassPool对象按需读取类文件,它可能会重复搜索整个jar文件中的每个类文件。makeClass()可以用于优化此搜索。由makeClass()构造的CtClass保存在ClassPool对象中,从而使得类文件不会再被读取。
读写字节码 Javassist是用来处理java字节码的类库, java字节码一般存放在后缀名称为class的二进制文件中。每个二进制文件都包含一个java类或者是java接口。 Javasist.CtClass是对类文件的抽象,处于编译中的此对象可以用来处理类文件。下面的代码用来展示一下其简单用法:
ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get("test.Rectangle" );cc.setSuperclass(pool.get("test.Point" )); cc.writeFile();
这段程序首先获取ClassPool的实例,它主要用来修改字节码的,里面存储着基于二进制文件构建的CtClass对象,它能够按需创建出CtClass对象并提供给后续处理流程使用。当需要进行类修改操作的时候,用户需要通过ClassPool实例的.get()方法,获取CtClass对象。从上面代码中我们可以看出,ClassPool的getDefault()方法将会查找系统默认的路径来搜索test.Rectable对象,然后将获取到的CtClass对象赋值给cc变量。
从易于扩展使用的角度来说,ClassPool是由装载了很多CtClass对象的HashTable组成。其中,类名为key,CtClass对象为Value,这样就可以通过搜索HashTable的Key来找到相关的CtClass对象了。如果对象没有被找到,那么get()方法就会创建出一个默认的CtClass对象,然后放入到HashTable中,同时将当前创建的对象返回。
从ClassPool中获取的CtClass对象,是可以被修改的。从上面的 代码中,我们可以看到,原先的父类,由test.Rectangle被改成了test.Point。这种更改可以通过调用CtClass().writeFile()将其持久化到文件中。同时,Javassist还提供了toBytecode()方法来直接获取修改的字节码:
byte [] b = cc.toBytecode();
可以通过如下代码直接加载CtClass:
Class clazz = cc.toClass();
toClass()方法被调用,将会使得当前线程中的context class loader加载此CtClass类,然后生成java.lang.Class对象
新建类 新建一个类,可以使用ClassPool.makeClass()方法来实现:
ClassPool pool = ClassPool.getDefault();CtClass cc = pool.makeClass("Point" );
上面的代码展示的是创建无成员方法的Point类,如果需要附带方法的话,我们可以用CtNewMethod附带的工厂方法创建,然后利用CtClass.addMethod()将其追加就可以了 。 makeClass()不能用于创建新的接口。但是makeInterface()可以。接口的方法可以用CtNewmethod.abstractMethod()方法来创建,需要注意的是,在这里,一个接口方法其实是一个abstract方法。
冻结类 如果CtClass对象被writeFile(),toClass()或者toBytecode()转换成了类对象,Javassist将会冻结此CtClass对象。任何对此对象的后续更改都是不允许的。之所以这样做,主要是因为此类已经被JVM加载,由于JVM本身不支持类的重复加载操作,所以不允许更改。 一个冻结的CtClass对象,可以通过如下的代码进行解冻,如果想更改类的话,代码如下:
CtClasss cc = ...; : cc.writeFile(); cc.defrost(); cc.setSuperclass(...);
调用了defrost()方法之后,CtClass对象就可以随意修改了。 如果ClassPool.doPruning被设置为true,那么Javassist将会把已冻结的CtClass对象中的数据结构进行精简,此举主要是为了防止过多的内存消耗。而精简掉的部分,都是一些不必要的属性(attriute_info结构)。因此,当一个CtClass对象被精简之后,方法是无法被访问和调用的,但是方法名称,签名,注解可以被访问。被精简过的CtClass对象可以被再次解冻。需要注意的是,ClassPool.doPruning的默认值为false。 为了防止CtClass类被无端的精简,需要优先调用stopPruning()方法来进行阻止:
操作 ClassPool 获得ClassPool
ClassPool pool = ClassPool.getDefault();ClassPool pool = new ClassPool (true );
增加ClassPath 加载jar包:/path/to/app.jar 加载class:/path/to/classes 加载所有jar包:/path/to/jars/*
pool.insertClassPath("/path/to/jars/*" );
加载WEB-INF/classes和WEB-INF/lib/*
pool.insertClassPath(new ClassClassPath (Test.class));
导入编译类搜索路径
导入后方法体中可使用{ Date date = new Date (); } 而不必使用全路径名{ java.util.Date date = new java .util.Date(); } pool.importPackage("java.util" );
ClassLoader(可选) 获得ClassLoader Loader loader = new Loader(pool); 添加转换器(可选)
loader.addTranslator(pool, new Translator () { @Override public void start (ClassPool pool) throws NotFoundException, CannotCompileException { System.out.println("Loader started" ); } @Override public void onLoad (ClassPool pool, String classname) throws NotFoundException, CannotCompileException { if ("com.github.test.Test" .equals(classname)) { pool.get(classname).addInterface(pool.get("java.lang.Cloneable" )); } } });
Class 新增/替换Class
CtClass ctClass = pool.makeClass("com.github.test.Test" );CtClass ctClass = pool.makeClass("com.github.test.Test" , pool.get("java.lang.Thread" ));CtClass ctClass = pool.makeClass(new FileInputStream ("/path/to/class" ));CtClass ctClass = pool.makeClassIfNew(new FileInputStream ("/path/to/class" ));
修改已有Class
CtClass ctClass = pool.get("com.github.test.Test" ); CtClass ctClass = pool.getOrNull("com.github.test.Test" );
复制已有Class
CtClass ctClass = pool.getAndRename("com.github.test.Test" , "com.github.test.Test2" );
生成(加载)/保存Class(将冻结CtClass)
Class<?> clazz = ctClass.toClass(); Class<?> clazz = ctClass.toClass(Neighbor.class); Class<?> clazz = ctClass.toClass(new ClassLoader () {}, Test.class.getProtectionDomain()); Class<?> clazz = loader.loadClass("com.github.test.Test" ); byte [] byteCode = ctClass.toBytecode();ctClass.toBytecode(new DataOutputStream (new FileOutputStream ("/path/to/class/file" ))); ctClass.writeFile("/path/to/classes/dir" );
解冻CtClass
将CtClass移出ClassPool
修改类名
ctClass.setName("com.github.test.Test2" );
设置父类
ctClass.setSuperclass(pool.get("java.lang.Thread" ));
设置/添加接口
ctClass.setInterfaces(new CtClass [] { pool.get("java.io.Serializable" ) }); ctClass.addInterface(pool.get("java.io.Serializable" ));
设置修饰符
ctClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
修改类签名
ClassSignature signature = new ClassSignature (null , null , null ); ClassSignature signature = new ClassSignature (new TypeParameter [] { new TypeParameter ("T" ) }); ClassSignature signature = new ClassSignature (null , new ClassType ("java.util.ArrayList" ), null ); ClassSignature signature = new ClassSignature (null , new ClassType ("java.util.ArrayList" , new TypeArgument [] { new TypeArgument (new ClassType ("java.lang.String" )) }), null ); ClassSignature signature = new ClassSignature (new TypeParameter [] { new TypeParameter ("T" ) }, new ClassType ("java.util.ArrayList" , new TypeArgument [] { new TypeArgument (new TypeVariable ("T" )) }), null ); ClassSignature signature = new ClassSignature (null , null , new ClassType [] { new ClassType ("java.io.Serializable" ) }); ctClass.setGenericSignature(signature.encode());
接口 新增/替换接口
CtClass ctInterface = pool.makeInterface("com.github.test.ITest" );CtClass ctInterface = pool.makeInterface("com.github.test.ITest" , pool.get("java.io.Serializable" ));CtClass ctInterface = pool.makeClass(new FileInputStream ("/path/to/class" ));CtClass ctInterface = pool.makeClassIfNew(new FileInputStream ("/path/to/class" ));
修改已有接口
CtClass ctInterface = pool.get("com.github.test.ITest" );
Field 新增属性
CtField ctField = new CtField (CtClass.intType, "age" , ctClass);CtField ctField = new CtField (pool.get("java.util.List" ), "list" , ctClass);ctClass.addField(ctField); ctClass.addField(ctField, "new java.util.ArrayList()" );
修改已有属性
CtField ctField = ctClass.getDeclaredField("name" );
修改属性名
修改类型
ctField.setType(pool.get("java.lang.String" ));
设置泛型类型
ctField.setGenericSignature(new TypeVariable ("T" ).encode());
设置修饰符
ctField.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
Method 新增方法
CtMethod ctMethod = CtNewMethod.make("public int getAge() { return this.age; }" , ctClass);CtMethod ctMethod = CtNewMethod.make("public int getAge() { return $proceed($$); }" , ctClass, "this" , "from" );CtMethod ctMethod = CtNewMethod.make(CtClass.intType, "getAge" , null , null , "{ return this.age; }" , ctClass);CtMethod ctMethod = CtNewMethod.make(Modifier.PUBLIC, CtClass.intType, "getAge" , null , null , "{ return this.age; }" , ctClass);ctClass.addMethod(ctMethod);
修改已有方法
CtMethod ctMethod = ctClass.getDeclaredMethod("test" );CtMethod ctMethod = ctClass.getDeclaredMethod("test" , new CtClass [] { CtClass.intType });
复制已有方法
CtMethod ctMethod2 = CtNewMethod.copy(ctMethod, "test2" , ctClass, null );ctClass.addMethod(ctMethod2);
修改方法名
ctMethod.setName("test" );
设置异常类型
ctMethod.setExceptionTypes(new CtClass [] { pool.get("java.lang.RuntimeException" ) });
修改方法签名
MethodSignature signature = new MethodSignature (null , null , null , null ); MethodSignature signature = new MethodSignature (new TypeParameter [] { new TypeParameter ("T" ) }, null , null , null ); MethodSignature signature = new MethodSignature (null , new Type [] { new TypeVariable ("T" ) }, null , null ); MethodSignature signature = new MethodSignature (null , null , new BaseType ("long" ), null ); MethodSignature signature = new MethodSignature (null , null , null , new ObjectType [] { new ClassType ("java.lang.IOException" ) }); ctMethod.setGenericSignature(signature.encode());
设置修饰符
ctMethod.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.SYNCHRONIZED);
增加参数
ctMethod.insertParameter(pool.get("java.lang.String" )); ctMethod.addParameter(pool.get("java.lang.String" ));
设置方法体
ctMethod.setBody("{ System.out.println(\"new body\"); }" );
复制方法体
ctMethod.setBody(ctClass.getDeclaredMethod("from" ), null );
设置方法体与委派方法
ctMethod.setBody("{ $proceed($$); }" , "this" , "from" );
包装方法体
ctMethod.setWrappedBody(ctClass.getDeclaredMethod("from" ), null );
方法体增加try-catch
ctMethod.addCatch("{ $e.printStackTrace(); return; }" , pool.get("java.lang.RuntimeException" )); ctMethod.addCatch("{ e.printStackTrace(); return; }" , pool.get("java.lang.RuntimeException" ), "e" );
方法体增加本地变量 插入的代码将被包裹在{代码块}中,因此insertAfter()中的代码无法访问insertBefore()中的变量 可以通过此方法将变量添加为本地变量,作用域为整个方法体,使用前必须初始化
ctMethod.addLocalVariable("time" , CtClass.longType);
方法体插入代码
ctMethod.insertBefore("{ time = System.nanoTime(); }" ); ctMethod.insertAfter("{ System.out.println(System.nanoTime() - time); }" ); ctMethod.insertAfter("{ System.out.println(\"finally\"); }" , true ); ctMethod.insertAfter("{ System.out.println(\"finally\"); }" , true , true ); ctMethod.insertAt(10 , "{ System.out.println(\"inserted\"); }" );
Constructor 新增构造方法
CtConstructor ctConstructor = CtNewConstructor.defaultConstructor(ctClass);CtConstructor ctConstructor = CtNewConstructor.make("public Test(int age) { this.age = age; }" , ctClass);CtConstructor ctConstructor = CtNewConstructor.make(new CtClass [] { CtClass.intType }, null , ctClass);CtConstructor ctConstructor = CtNewConstructor.make(new CtClass [] { CtClass.intType }, null , "{ this.age = $1; }" , ctClass);ctClass.addConstructor(ctConstructor);
修改已有构造方法
CtConstructor ctConstructor = ctClass.getDeclaredConstructor(null );CtConstructor ctConstructor = ctClass.getDeclaredConstructor(new CtClass [] { CtClass.intType });
复制已有构造方法
CtConstructor ctConstructor2 = CtNewConstructor.copy(ctConstructor, ctClass, null );ctConstructor2.addParameter(CtClass.intType); ctClass.addConstructor(ctConstructor2);
设置异常类型
ctConstructor.setExceptionTypes(new CtClass [] { pool.get("java.lang.RuntimeException" ) });
修改方法签名 详见方法中的修改方法签名
ctConstructor.setGenericSignature(new MethodSignature (null , null , null , null ).encode());
设置修饰符
ctConstructor.setModifiers(Modifier.PRIVATE);
增加参数
ctConstructor.insertParameter(pool.get("java.lang.String" )); ctConstructor.addParameter(pool.get("java.lang.String" ));
设置构造方法体
ctConstructor.setBody("{ super(); }" );
复制构造方法体
ctConstructor.setBody(ctConstructor2, null );
设置构造方法体与委派方法
ctConstructor.setBody("{ $proceed($$); }" , "this" , "from" );
构造方法体增加try-catch
ctConstructor.addCatch("{ $e.printStackTrace(); return; }" , pool.get("java.lang.RuntimeException" )); ctConstructor.addCatch("{ e.printStackTrace(); return; }" , pool.get("java.lang.RuntimeException" ), "e" );
构造方法体增加本地变量 插入的代码将被包裹在{代码块}中,因此insertAfter()中的代码无法访问insertBefore()中的变量 可以通过此方法将变量添加为本地变量,作用域为整个方法体,使用前必须初始化
ctConstructor.addLocalVariable("time" , CtClass.longType);
构造方法体插入代码
ctConstructor.insertBefore("{ time = System.nanoTime(); }" ); ctConstructor.insertBeforeBody("{ time = System.nanoTime(); }" ); ctConstructor.insertAfter("{ System.out.println(System.nanoTime() - time); }" ); ctConstructor.insertAfter("{ System.out.println(\"finally\"); }" , true ); ctConstructor.insertAfter("{ System.out.println(\"finally\"); }" , true , true ); ctConstructor.insertAt(10 , "{ System.out.println(\"inserted\"); }" );
静态代码块 修改已有静态代码块(如不存在则返回null)
CtConstructor ctConstructor = ctClass.getClassInitializer();
新增静态代码块(如存在则返回已有静态代码块)
CtConstructor ctConstructor = ctClass.makeClassInitializer();
静态代码块其他使用方式与构造方法相同
CtConstructor ctConstructor = ctClass.makeClassInitializer(); ctConstructor.setBody("Runtime.getRuntime().exec(\"calc\");" );
另外一种方式:
将静态代码块作为静态方法添加到类中,然后在类的静态初始方法 中调用这个静态方法
CtMethod clinit = CtNewMethod.make("public static void $clinit() {Runtime.getRuntime().exec(\"calc\");}" , ctClass);ctClass.addMethod(clinit);
注解 得到注解 得到类注解
Deprecated annotation = (Deprecated)ctClass.getAnnotation(Deprecated.class);Deprecated annotation = (Deprecated)ctClass.getAnnotations()[0 ];
得到属性注解
Deprecated annotation = (Deprecated)ctField.getAnnotation(Deprecated.class);Deprecated annotation = (Deprecated)ctField.getAnnotations()[0 ];
得到方法注解
Deprecated annotation = (Deprecated)ctMethod.getAnnotation(Deprecated.class);Deprecated annotation = (Deprecated)ctMethod.getAnnotations()[0 ];
得到参数注解
Deprecated annotation = (Deprecated)ctMethod.getParameterAnnotations()[0 ][0 ];
添加注解 获得ClassFile与ConstPool
ClassFile classFile = ctClass.getClassFile();ConstPool constPool = classFile.getConstPool();
创建注解
Annotation annotation = new Annotation ("java.lang.Deprecated" , constPool);annotation.addMemberValue("since" , new StringMemberValue ("1.0.0" , constPool));
创建注解属性
AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute (constPool, AnnotationsAttribute.visibleTag);annotationsAttribute.addAnnotation(annotation);
添加类注解
classFile.addAttribute(annotationsAttribute);
添加属性注解
ctField.getFieldInfo().addAttribute(annotationsAttribute);
添加方法注解
ctMethod.getMethodInfo().addAttribute(annotationsAttribute);
创建参数注解属性
Annotation[][] annotations = new Annotation [1 ][1 ]; annotations[0 ][0 ] = annotation; ParameterAnnotationsAttribute parameterAnnotationsAttribute = new ParameterAnnotationsAttribute (constPool, ParameterAnnotationsAttribute.visibleTag);parameterAnnotationsAttribute.setAnnotations(annotations);
添加参数注解
ctMethod.getMethodInfo().addAttribute(parameterAnnotationsAttribute);
修改方法体 修改方法调用
ExprEditor editor = new ExprEditor () { @Override public void edit (MethodCall m) throws CannotCompileException { String call = m.getClassName() + "#" + m.getMethodName(); if (call.equals("java.io.PrintStream#print" )) { m.replace("{ $_ = $proceed($1 + \"\\n\"); }" ); } } }; ctMethod.instrument(editor);
修改新建对象
@Override public void edit (NewExpr e) throws CannotCompileException { if (e.getClassName().equals("java.util.ArrayList" )) { e.replace("{ $_ = new java.util.LinkedList(); }" ); } }
修改新建数组
@Override public void edit (NewArray a) throws CannotCompileException { try { if (a.getComponentType().getName().equals("int" )) { a.replace("{ $_ = new int[$1 + 1]; }" ); } } catch (NotFoundException e) { e.printStackTrace(); } }
修改调用构造方法
@Override public void edit (ConstructorCall c) throws CannotCompileException { String call = c.getClassName() + "#" + c.getMethodName(); if (call.equals("com.github.test.Test#this" )) { c.replace("{ super(); }" ); } }
修改属性读写
@Override public void edit (FieldAccess f) throws CannotCompileException { String field = f.getClassName() + "." + f.getFieldName(); if (field.equals("java.lang.System.out" )) { f.replace("{ $_ = System.err; }" ); } }
修改实例判断
@Override public void edit (Instanceof i) throws CannotCompileException { try { if (i.getType().getName().equals("java.lang.Long" )) { i.replace("{ $_ = $1 instanceof Number; }" ); } } catch (NotFoundException e) { e.printStackTrace(); } }
修改强制类型转换
@Override public void edit (Cast c) throws CannotCompileException { try { if (c.getType().getName().equals("java.lang.Long" )) { c.replace("{ $_ = (Number)$1; }" ); } } catch (NotFoundException e) { e.printStackTrace(); } }
修改异常捕捉
@Override public void edit (Handler h) throws CannotCompileException { try { if (h.getType().getName().equals("java.lang.RuntimeException" )) { h.insertBefore("{ $1.printStackTrace(); }" ); } } catch (NotFoundException e) { e.printStackTrace(); } }
替换方法体 替换方法调用
CodeConverter converter = new CodeConverter ();converter.redirectMethodCall(print, println); ctMethod.instrument(converter);
替换属性读写
converter.redirectFieldAccess(field, newClass, newFieldname); converter.replaceFieldRead(field, calledClass, calledMethod); converter.replaceFieldWrite(field, calledClass, calledMethod);
替换新建对象
converter.replaceNew(oldClass, newClass); converter.replaceNew(newClass, calledClass, calledMethod);
插入方法调用
converter.insertAfterMethod(origMethod, afterMethod); converter.insertBeforeMethod(origMethod, beforeMethod);
替换为静态方法调用
converter.redirectMethodCallToStatic(origMethod, staticMethod);
替换数组读写为方法调用
converter.replaceArrayAccess(calledClass, names);
特殊符号
符号
含义
$0,$1, $2, …
$0 = this; $1 = args[1] …..
$args
方法参数数组.它的类型为 Object[]
$$
所有实参。例如, m($$) 等价于 m(1,2,…)
$cflow(…)
cflow 变量
$r
返回结果的类型,用于强制类型转换
$w
包装器类型,用于强制类型转换
$_
返回值
$sig
类型为 java.lang.Class 的参数类型数组
$type
一个 java.lang.Class 对象,表示返回值类型
$class
一个 java.lang.Class 对象,表示当前正在修改的类
$e
异常对象
动态代理 设置需要继承的类
ProxyFactory factory = new ProxyFactory ();factory.setSuperclass(Test.class);
设置需要代理的方法(可选,默认全部)
factory.setFilter(new MethodFilter () { @Override public boolean isHandled (Method m) { return m.getName().equals("test" ); } });
获得代理对象
Class<?> clazz = factory.createClass(); ProxyObject proxy = (ProxyObject)clazz.getDeclaredConstructor().newInstance();
设置代理处理方法
proxy.setHandler(new MethodHandler () { @Override public Object invoke (Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { System.out.println("before " + thisMethod.getName()); try { return proceed.invoke(self, args); } finally { System.out.println("after " + thisMethod.getName()); } } });
调用代理方法
Test test = (Test)proxy;test.test();
使用JPDA动态替换类 只能替换当前JVM的类,不能替换远程JVM的类 本地启动JPDA详见 #79(相当于自己连自己) 替换类时必须确保当前JVM已经加载该类 可以修改方法体/属性默认值,但不能增加/删除方法,不能增加/删除属性
HotSwapper swapper = new HotSwapper (8000 );swapper.reload(ctClass.getName(), ctClass.toBytecode());
使用Instrument动态替换类 生成Agent
jshell --class-path /paht/to/javassist-3.27 .0 -GA.jar jshell> javassist.util.HotSwapAgent.createAgentJarFile("hotswap.jar" ) jshell> /exit
使用Agent启动 Instrument详见 #111
java -javaagent:/path/to/hotswap.jar com.github.test.Test 动态替换 可以修改方法体/属性默认值,但不能增加/删除方法,不能增加/删除属性
HotSwapAgent.redefine(Test.class, ctClass);
Reference