javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。性能比BCEL、ASM等要差,但是使用不需要了解虚拟机指令。使用简单且快速。
操作字节码的可实现的功能
- 动态生成新的类
- 动态改变某个类的结构(添加/删除/修改 新的属性/方法)
与反射相较的优势
比反射开销小,性能高。
javassist的使用
1.导入javassist的jar包
可以通过官网找到 http://www.javassist.org/
也可以通过maven直接依赖
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.26.0-GA</version>
</dependency>
2.通过javassist来创建一个class
//获取Class池
ClassPool pool = ClassPool.getDefault();
//生成一个新的CtClass
CtClass cclz = pool.makeClass("com.rainc.bean.Emp");
//通过make创建属性 直接输入源码即可
CtField f1 = CtField.make("private int empno;", cclz);
//将属性添加至CtClass
cclz.addField(f1);
//通过构造器创建属性 基本类型可以通过CtClass.来获得如CtClass.intType,若是其它类型则要通过pool.get
CtField f2 = new CtField(pool.get("java.lang.String"), "ename", cclz);
//设置修饰符
f2.setModifiers(Modifier.PRIVATE);
cclz.addField(f2);
//通过make创建方法
CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cclz);
//通过构造器创建方法
CtMethod m2 = new CtMethod(CtClass.voidType, "setEmpno", new CtClass[]{CtClass.intType}, cclz);
m2.setModifiers(Modifier.PUBLIC);
//$0相当于this,$1开始表示的是第几个形参$1是第一个$2是第二个。
m2.setBody("{$0.empno=$1;}");
cclz.addMethod(m1);
cclz.addMethod(m2);
//如果是某个属性的get和set方法还可以通过CtNewMethod类来创建
CtMethod m3 = CtNewMethod.getter("getEname", f2);
CtMethod m4 = CtNewMethod.setter("setEname", f2);
cclz.addMethod(m3);
cclz.addMethod(m4);
//添加构造器
CtConstructor constructor1 = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, cclz);
constructor1.setBody("{$0.empno=$1;$0.ename=$2;}");
CtConstructor constructor2 = CtNewConstructor.make("public Emp(){}", cclz);
cclz.addConstructor(constructor1);
cclz.addConstructor(constructor2);
cclz.writeFile("E:/myjava");//将上面构造好的类写入到E:/myjava里
System.out.println("生成类,成功");
通过反编译软件查看所生成的class文件
如果使用的是idea的话直接将class文件拖至idea即可,因为idea内置有反编译。
package com.rainc.bean;
public class Emp {
private int empno;
private String ename;
public int getEmpno() {
return this.empno;
}
public void setEmpno(int var1) {
this.empno = var1;
}
public String getEname() {
return this.ename;
}
public void setEname(String var1) {
this.ename = var1;
}
public Emp(int var1, String var2) {
this.empno = var1;
this.ename = var2;
}
public Emp() {
}
}
3.通过反射来调用javassist生成的class类
//接之前创建class的代码
//获得Class类
Class clz = cclz.toClass();
Object obj = clz.getConstructor().newInstance();
//取得setEname方法
Method method1 = clz.getMethod("setEname", String.class);
//调用setEname方法
method1.invoke(obj, "rainc");
//取得getEname方法
Method method2 = clz.getMethod("getEname");
//调用getEname方法并取得返回值
Object result = method2.invoke(obj);
System.out.println(result);
可以看到成功的设置了属性值并取得了,红色的是高版本java反射的警告忽视即可。
4.通过javassist操作已存在的类
创建测试用的类
package com.rainc.test;
/**
* @Author rainc
* @create 2019/10/25 19:39
*/
public class Emp {
private String ename;
private int empno;
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public int getEmpno() {
return empno;
}
public void setEmpno(int empno) {
this.empno = empno;
}
public Emp(String ename, int empno) {
this.ename = ename;
this.empno = empno;
}
public Emp() {
}
public void hello(){
System.out.println("helloWorld");
}
}
得到类的信息
ClassPool pool = ClassPool.getDefault();
//通过类全名得到该类
CtClass ctclz = pool.get("com.rainc.test.Emp");
System.out.println(ctclz.getName());//获得类全名
System.out.println(ctclz.getSimpleName());//获得类名
System.out.println(ctclz.getSuperclass());//获得父类
System.out.println(ctclz.getInterfaces());//获得接口
生成新的方法
ClassPool pool = ClassPool.getDefault();
//通过类全名得到该类
CtClass ctclz = pool.get("com.rainc.test.Emp");
//添加了一个求和的方法
CtMethod m = CtMethod.make("public int sum(int a,int b){return a+b;}", ctclz);
ctclz.addMethod(m);
//通过反射调用新生成的方法
Class clz = ctclz.toClass();
Object obj = clz.getConstructor().newInstance();
Method method = clz.getMethod("sum", int.class, int.class);
Object result = method.invoke(obj, 200, 300);
System.out.println(result);
修改已有的方法
ClassPool pool = ClassPool.getDefault();
//通过类全名得到该类
CtClass ctclz = pool.get("com.rainc.test.Emp");
//取得指定的方法
CtMethod m = ctclz.getDeclaredMethod("hello");
//在前面插入
m.insertBefore("System.out.println(\"start!!!\");");
//在尾部插入
m.insertAfter("System.out.println(\"-----end----\");");
//在class的第几行前插入,这个插入的行数是指java源文件的行数我这里输出helloworld在37行
m.insertAt(36, "System.out.println(\"-----insert36----\");");
m.insertAt(37, "System.out.println(\"-----insert37----\");");
//通过反射调用新生成的方法
Class clz = ctclz.toClass();
Object obj = clz.getConstructor().newInstance();
Method method = clz.getMethod("hello");
method.invoke(obj);
其它的都大同小异,但是通过提供的api无法操作泛型。注解只能读取无法直接进行修改,需要通过字节码来实现。
学习参考: https://www.bilibili.com/video/av30023103/?p=278
个人博客: https://www.rainc.top/2019/10/26/java-study/Javassist-study/