官网
http://www.javassist.org/tutorial/tutorial.html
概述
Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
Javassist简单易用, 快速。
Javassist作用
- 运行时监控插桩埋点
- AOP动态代理实现(性能上比Cglib生成的要慢)
- 获取访问类结构信息:如获取参数名称信息
常用API
Javassist 语法
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中不存在这个类的实例对象。修改方法的实现必须在修改的类加载之前进行
参考
https://baijiahao.baidu.com/s?id=1660843613132087355&wfr=spider&for=pc
https://www.cnblogs.com/scy251147/p/11100961.html
https://blog.csdn.net/21aspnet/article/details/81671777
https://www.cnblogs.com/rickiyang/p/11336268.html