概述
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
Javassist简单易用, 快速。
Javassist作用
- 运行时监控插桩埋点
- AOP动态代理实现(性能上比Cglib生成的要慢)
- 获取访问类结构信息:如获取参数名称信息
常用API
类 | 说明 |
---|---|
ClassPool | Javassist的类池,使用ClassPool 类可以跟踪和控制所操作的类, 与 JVM ClassLoader相似 |
CtClass | CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法。 |
CtMethod | 类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码 |
CtConstructor | 构造函数 |
CtField | 类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等 |
Javassist 语法
项目 | Value |
---|---|
$0, $1, $2, … | this and actual parameters |
$args | An array of parameters. The type of $args is Object[]. |
$$ | All actual parameters.For example, m($$) is equivalent to m($1,$2,…) |
$cflow(…) | cflow variable |
$r | The result type. It is used in a cast expression. |
$w | The wrapper type. It is used in a cast expression. |
$_ | The resulting value |
$sig | An array of java.lang.Class objects representing the formal parameter types |
$type | A java.lang.Class object representing the formal result type. |
$class | A java.lang.Class object representing the class currently edited. |
Javassist使用流程
Demo
依赖
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.18.1-GA</version> </dependency>
import javassist.*;/** * 使用Javassist 构建 一个新的类 并执行 */public class FirstJavasisit { public static void main(String[] args) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException { ClassPool pool = new ClassPool(true); // 插入类路径,通过类路径去搜索我们要的类 pool.insertClassPath(new LoaderClassPath(FirstJavasisit.class.getClassLoader())); // 构建一个新的CtClass对象 CtClass targetClass = pool.makeClass("com.artisan.Hello"); // 实现一个接口 targetClass.addInterface(pool.get(IHello.class.getName())); // 获取返回类型 CtClass returnType = pool.get(void.class.getName()); // 方法名称 String mname = "sayHello"; // 方法参数 CtClass[] parameters = new CtClass[]{pool.get(String.class.getName())}; // 实例化方法 CtMethod method = new CtMethod(returnType, mname, parameters, targetClass); // 方法中的源码 String src = "{" + "System.out.println($1);" + "}"; // 设置src到方法中 method.setBody(src); // 添加方法 targetClass.addMethod(method); // 装在到当前的ClassLoader中 Class cla = targetClass.toClass(); // 实例化 IHello hello = (IHello) cla.newInstance(); // 方法调用 hello.sayHello("artisan"); } /** * 接口不是必须的,只是为了方便演示,少写点反射代码 */ public interface IHello { void sayHello(String name); }}
Demo2
让我们对UserService类 插装一下
package com.artisan.agent;public class UserService { /** * 无参方法 * @throws InterruptedException */ public void sayHello() throws InterruptedException { Thread.sleep(100); System.out.println("hello 小工匠"); } /** * 无返回值的 * @param name * @param age * @param other * @throws InterruptedException */ public void say2Void(String name,int age,Object other) throws InterruptedException { Thread.sleep(100); System.out.println("hello 小工匠2"); } /** * 带有返回值 * @param name * @param age * @param other * @return * @throws InterruptedException */ public String say2(String name,int age,Object other) throws InterruptedException { Thread.sleep(100); System.out.println("hello 小工匠2 with return "); return "ttttt"; }}
@Test public void test3() throws NotFoundException, CannotCompileException, InterruptedException { // 类加载器 ClassPool classPool = new ClassPool(); // 追加系统ClassLoader classPool.appendSystemPath(); // 获取要操作的类 CtClass ctClass = classPool.get("com.artisan.agent.UserService"); // 获取方法 CtMethod originMethod = ctClass.getDeclaredMethod("say2Void"); // copy 一个新的方法 CtMethod newMethod = CtNewMethod.copy(originMethod,ctClass,null); // 设置新名字 originMethod.setName(originMethod.getName()+ "$agent"); // 对原方法进行包装,比如加计算方法耗时 newMethod.setBody("{ long begin = System.currentTimeMillis();\n" + " say2Void$agent($$);\n" + " long end = System.currentTimeMillis();\n" + " System.out.println(end - begin);" + "}"); // 将新方法添加到单签类中 ctClass.addMethod(newMethod); //把修改后的class装载到JVM ctClass.toClass(); new com.artisan.agent.UserService().say2Void("art2",18,"xxxx"); } @Test public void test4() throws NotFoundException, CannotCompileException, InterruptedException { // 类加载器 ClassPool classPool = new ClassPool(); // 追加系统ClassLoader classPool.appendSystemPath(); // 获取要操作的类 CtClass ctClass = classPool.get("com.artisan.agent.UserService"); // 获取方法 CtMethod originMethod = ctClass.getDeclaredMethod("say2"); // copy 一个新的方法 CtMethod newMethod = CtNewMethod.copy(originMethod,ctClass,null); // 设置新名字 originMethod.setName(originMethod.getName()+ "$agent"); // 对原方法进行包装,比如加计算方法耗时 带有返回值的的 $r newMethod.setBody("{ long begin = System.currentTimeMillis();\n" + " say2$agent($$);\n" + " long end = System.currentTimeMillis();\n" + " System.out.println(end - begin);" + " Object s = \"test\" ;" + " return ($r)s ;" + "}"); // 将新方法添加到单签类中 ctClass.addMethod(newMethod); //把修改后的class装载到JVM ctClass.toClass(); System.out.println((new com.artisan.agent.UserService().say2("art2", 18, "xxxx"))); }
注意事项
- 所引用的类型,必须通过ClassPool获取后才可以使用
- 代码块中所用到的引用类型,使用时必须写全量类名
- 代码块内容写错了,只有在运行时才报错
- javassist只接受单个语句或用大括号括起来的语句块
- 动态修改的类,必须在修改之前,jvm中不存在这个类的实例对象。修改方法的实现必须在修改的类加载之前进行