Javassist是用来处理java字节码的类库, java字节码一般存放在后缀名称为class的二进制文件中。每个二进制文件都包含一个java类或者是java接口。
Javassist主要涉及ClassPool、CtClass、CtMethod、 CtConstructor、CtField等几个类,下面看代码及注释体会各类的含义:
首先在pom.xml中添加Javassist依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
一、新增一个类并保存:
public static void buildUser() throws Exception {
ClassPool pool = ClassPool.getDefault();
//导入包,告诉编译器在解析类名时搜索其他包
//pool.importPackage("ml.code.UserUtils");
//创建一个空类
CtClass clazz = pool.makeClass("ml.code.User");
//继承类
// clazz.setSuperclass(pool.get("ml.code.UserUtils"));
//新增一个字段 private String name
CtField ctField = new CtField(pool.get("java.lang.String"), "name", clazz);
// 访问级别是 private
ctField.setModifiers(Modifier.PRIVATE);
// 初始值是 "毛毛"
clazz.addField(ctField, CtField.Initializer.constant("毛毛3"));
// 3. 生成 getter、setter 方法
clazz.addMethod(CtNewMethod.setter("setName", ctField));
clazz.addMethod(CtNewMethod.getter("getName", ctField));
// 4. 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{
}, clazz);
cons.setBody("{name = \"小张\";}");
clazz.addConstructor(cons);
// 5. 添加有参的构造函数
cons = new CtConstructor(new CtClass[]{
pool.get("java.lang.String")}, clazz);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.name = $1;}");
clazz.addConstructor(cons);
// 6. 创建一个名为printName方法,无参数,无返回值,输出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{
}, clazz);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
clazz.addMethod(ctMethod);
//这里会将这个创建的类对象编译为.class文件,生成之后在Java\IdeaProjects\comm\esa2000\target\classes\\ml\\code这个文件夹下面可以找到编译后的类
//保存时此处的路径会自动加上pool.makeClass("ml.code.User")中的包路径
clazz.writeFile();
// clazz.writeFile("E:\\Java\\IdeaProjects\\comm\\target\\classes");
// pool.appendClassPath("E:\\Java\\IdeaProjects\\comm\\target\\classes\\");
System.out.println(clazz.isFrozen());
pool=null;
pool=ClassPool.getDefault();
clazz = pool.get("ml.code.User");
// 这里不写入文件,直接实例化
Object person = clazz.toClass().newInstance();
// 设置值
Method setName = person.getClass().getMethod("setName", String.class);
setName.invoke(person, "小王3");
// 输出值
Method execute = person.getClass().getMethod("printName");
execute.invoke(person);
}
如果在新增类中使用其他类,需要使用pool.importPackage("ml.code.UserUtils");
先进行包的导入;
如果CtClass对象被writeFile(),toClass()或者toBytecode()转换成了类对象,Javassist将会冻结此CtClass对象。任何对此对象的后续更改都是不允许的。之所以这样做,主要是因为此类已经被JVM加载,由于JVM本身不支持类的重复加载操作,所以不允许更改。
所以在writeFile(),toClass()或者toBytecode()之后需要继续修改类,然后我在调用pool.get()之前,先调用代码:
if(ctClass.isFrozen()){
ctClass.defrost();
}
clazz.writeFile();
方法是直接保存在程序类目录下,等同于clazz.writeFile(".");
注意是保存在程序类目录下,具体生成位置在类目录下的新增类的包路径下。
如果修改保存,可以使用下面语句获程序的classPath路径
String path=clazz.toClass().getResource("/log4j2.xml").getPath();
然后在path中截取classPath路径。注意不使用"/" 这就是相对路径。也就是log4j2.xml所在的包路径名称都会被获取。
二、修改一个类
public static void modifyUser()throws Exception{
ClassPool pool = ClassPool.getDefault();
/*
*由静态方法ClassPool.getDefault()返回的默认ClassPool与JVM有相同的扫描路径。
* 如果程序在JBoss或者Tomcat之类的web应用程序中,
* ClassPoll对象可能无法找到用户的类,因为这些Web应用服务器使用多个类加载器以及系统类加载器。
* 在这种情况下,就必须注册额外的类扫描路径到ClassPool中。可以使用如下方式进行注册:
* 参考:https://www.jianshu.com/p/7323e7bc9a3c
*/
// pool.insertClassPath(new ClassClassPath(UserUtils.class.getClass()));
CtClass clazz = pool.get("ml.code.User");
System.out.println(clazz.isFrozen());
//防止CtClass被精简,手动开启关闭精简,具体含义看最后引用文章
clazz.stopPruning(true);
CtMethod madeUser = clazz.getDeclaredMethod("madeUser");
madeUser.insertBefore("System.out.println(\"制造开始前\");");
madeUser.insertAfter("System.out.println(\"成功制造。。。。\");");
//新增一个方法
CtMethod ctMethod = new CtMethod(pool.get("java.lang.String"), "modifyAge", new CtClass[]{
}, clazz);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(\"我的年龄是:20\");System.out.println(\"我的年龄是:20\");return \"ss\";}");
clazz.addMethod(ctMethod);
//clazz.writeFile();
clazz.writeFile("E:\\Java\\IdeaProjects\\comm\\target\\classes");
// clazz.writeFile("E:\\Java\\IdeaProjects\\comm\\target\\classes");
//clazz.detach();
// clazz = clazz.;
//ClassLoader l= clazz.toClass().getClassLoader();
Object user = clazz.toClass().newInstance();
// 调用 personFly 方法
Method personFlyMethod = user.getClass().getMethod("madeUser");
personFlyMethod.invoke(user);
//调用 joinFriend 方法
Method execute = user.getClass().getMethod("modifyAge");
execute.invoke(user);
}
新增类时使用CtClass clazz = pool.makeClass("ml.code.User");
修改一个已存在类时使用CtClass clazz = pool.get("ml.code.User");
两个例子里最后的代码都是使用反射去调取修改后的类,进行执行测试。
看两个引用介绍,写的比较详细,一个是中文简单翻译,一个是原文。
https://www.cnblogs.com/scy251147/p/11100961.html
http://www.javassist.org/tutorial/tutorial.html