官网
http://www.javassist.org/
http://www.javassist.org/tutorial/tutorial.html
概述
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中不存在这个类的实例对象。修改方法的实现必须在修改的类加载之前进行
参考
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