javassist操作示例

一、 调用生成的类对象
1. 通过反射的方式调用
2. 通过读取 .class 文件的方式调用
3. 通过接口的方式
二、创建对象(动态创建对象)
动态创建对象,动态创建一个Teacher对象,为其增加2个方法:
package com.dxz;

import com.dxz.dto.GoodStudent;
import javassist.*;
import org.junit.Test;

import java.lang.reflect.Method;

public class Create {
    @Test
    public void test1() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        // 创建teacher类
        CtClass teacherClass = pool.makeClass("com.dxz.dto.Teacher");
        // 设置为公有类
        teacherClass.setModifiers(Modifier.PUBLIC);
        // 获取String类型
        CtClass stringClass = pool.get("java.lang.String");
        // 获取list类型
        CtClass listClass = pool.get("java.util.List");
        // 获取学生的类型
        CtClass studentClass = pool.get("com.dxz.dto.GoodStudent");
        // 给teacher添加name属性
        CtField nameField = new CtField(stringClass, "name", teacherClass);
        nameField.setModifiers(Modifier.PUBLIC);
        teacherClass.addField(nameField);
        // 给teacher类添加students属性
        CtField studentList = new CtField(listClass, "students", teacherClass);
        studentList.setModifiers(Modifier.PUBLIC);
        teacherClass.addField(studentList);
        // 给teacher类添加无参构造方法
        CtConstructor ctConstructor = CtNewConstructor.make("public Teacher() {this.name =\"abc\";this.students = new java.util.ArrayList();}", teacherClass);
        teacherClass.addConstructor(ctConstructor);
        // 给teacher类添加addStudent方法
        CtMethod m = new CtMethod(CtClass.voidType, "addStudent", new CtClass[]{studentClass}, teacherClass);
        m.setModifiers(Modifier.PUBLIC);
        // 添加学生对象到students属性中, $1代表参数1
        m.setBody("this.students.add($1);");
        teacherClass.addMethod(m);
        // 给teacher类添加sayHello方法
        m = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{}, teacherClass);
        m.setModifiers(Modifier.PUBLIC);
        m.setBody("System.out.println(\"Hello, My name is \" + this.name);");
        m.insertAfter("System.out.println(\"I have \" + this.students.size() + \" students\");");
        teacherClass.addMethod(m);
        // 加载修改后的类
        Class<?> cls = teacherClass.toClass();
        // 实例teacher对象
        Object teacher = cls.newInstance();
        // 获取teacher的addStudent方法
        Method method = cls.getDeclaredMethod("addStudent", GoodStudent.class);
        System.out.println("invoke1 begin----------");
        Object invoke1 = method.invoke(teacher, new GoodStudent());

        //获取teacher的sayHellow方法
        Method method2 = cls.getDeclaredMethod("sayHello");
        System.out.println("invoke2 begin----------");
        Object invoke2 = method2.invoke(teacher);

        //这里会将这个动态创建的类对象编译为.class文件
        teacherClass.writeFile("E:\\agentdemo-out\\");
    }
}
结果:
0
查看动态生产的Teacher如下:
0
 
三、 修改现有的类对象
前面说到新增一个类对象。这个使用场景目前还没有遇到过,一般会遇到的使用场景应该是修改已有的类。比如常见的日志切面,权限切面。我们利用javassist来实现这个功能。
四、新增一个方法
有如下类对象:
package com.dxz.service;

public class PersonService {
    public void getPerson(){
        System.out.println("get Person");
    }
    public void personFly(){
        System.out.println("oh my god,I can fly");
    }
}
然后对他进行修改:
package com.dxz;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

import java.lang.reflect.Method;

public class UpdatePerson {

    public static void update() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.dxz.service.PersonService");

        CtMethod personFly = cc.getDeclaredMethod("personFly");
        personFly.insertBefore("System.out.println(\"起飞之前准备降落伞\");");
        personFly.insertAfter("System.out.println(\"成功落地。。。。\");");


        //新增一个方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "joinFriend", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(\"i want to be your friend\");}");
        cc.addMethod(ctMethod);

        Object person = cc.toClass().newInstance();
        // 调用 personFly 方法
        Method personFlyMethod = person.getClass().getMethod("personFly");
        personFlyMethod.invoke(person);
        //调用 joinFriend 方法
        Method execute = person.getClass().getMethod("joinFriend");
        execute.invoke(person);
    }

    public static void main(String[] args) {
        try {
            update();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
结果:
0
在personFly方法前后加上了打印日志。然后新增了一个方法joinFriend。执行main函数可以发现已经添加上了。
另外需要注意的是:上面的insertBefore() 和 setBody()中的语句,如果你是单行语句可以直接用双引号,但是有多行语句的情况下,你需要将多行语句用{}括起来。javassist只接受单个语句或用大括号括起来的语句块
 
五、新增一个成员变量
package com.dxz;

import javassist.*;

import java.io.IOException;
import java.net.URL;

public class AddField {
    public static void main(String[] args) throws CannotCompileException, IOException {
        URL resource = AddField.class.getClassLoader().getResource("");
        String file = resource.getFile();
        System.out.println("文件存储路径:" + file);

        ClassPool cp = ClassPool.getDefault();
        CtClass ctClass = cp.makeClass("com.dxz.dto.Hello");

        //添加一个hello1的方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(ctMethod);

        //添加一个int类型的,名字为value的变量
        CtField ctField = new CtField(CtClass.intType, "value", ctClass);
        ctField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ctField);

        //保持为.class文件
        ctClass.writeFile(file);
    }
}
结果:
0
(六)、给变量新增get和set方法
package com.dxz;

import javassist.*;

import java.io.IOException;
import java.net.URL;

public class AddMethod {
    public static void main(String[] args) throws CannotCompileException, IOException, IOException {

        URL resource = AddMethod.class.getClassLoader().getResource("");
        String file = resource.getFile();
        System.out.println("文件存储路径:" + file);

        ClassPool cp = ClassPool.getDefault();
        CtClass ctClass = cp.makeClass("com.dxz.service.Hello");

        //添加一个hello1的方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "hello1", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(ctMethod);

        //添加一个int类型的,名字为value的变量
        CtField ctField = new CtField(CtClass.intType, "value", ctClass);
        ctField.setModifiers(Modifier.PRIVATE);
        ctClass.addField(ctField);

        //为value变量添加set方法
        CtMethod setValue = new CtMethod(CtClass.voidType, "setValue", new CtClass[]{CtClass.intType}, ctClass);
        setValue.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(setValue);

        //为value变量添加get方法
        CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
        getValue.setModifiers(Modifier.PUBLIC);
        ctClass.addMethod(getValue);
        //保持为.class文件
        ctClass.writeFile(file);
    }
}
结果
0
(七)、给方法内部添加代码
你是不是很好奇,set和get方法内部并没有代码,当程序运行的时候,肯定会出错的。
我们预想的结果:
  private int value;
    public void setValue(int var1) {
        this.value = var1;
    }
    public int getValue() {
        return this.value;
    }
修改如下:
//设置方法体
setValue.setBody("this.value = var1;");
ctClass.addMethod(setValue);

//为value变量添加get方法
CtMethod getValue = new CtMethod(CtClass.intType, "getValue", new CtClass[]{}, ctClass);
getValue.setModifiers(Modifier.PUBLIC);
//设置方法体
getValue.setBody("return this.value;");
ctClass.addMethod(getValue);
结果:
0
这个原因我们前面其实提到了,因为在编译的时候,会把变量名抹掉,传递的参数会依次在局部变量表中的顺序。
举个例子,如果要传递:
public void test(int a,int b,int c){     ... }
那么a,b,c就对应本地变量表中的1,2,3的位置:
0
那么我们获取变量时就不能使用原始的名字了,在Javassist中访问方法中的参数使用的是$1, 2 , 2, 2,…,而不是直接使用原始的名字。
//设置方法体
setValue.setBody("this.value = $1;");
新的结果:
0
再扩展一下,2个参数的情况:
//添加一个hello2的方法
CtMethod ctMethod2 = new CtMethod(CtClass.voidType, "hello2", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod2.setModifiers(Modifier.PUBLIC);
ctMethod2.setBody("this.value = $1 + $2;");
ctClass.addMethod(ctMethod2);
结果:
因为我们value是int,$1是int,$2是double,所以做了强制转型处理。
0
(八)、在方法体的前后分别插入代码
//添加一个hello3的方法
CtMethod ctMethod3 = new CtMethod(CtClass.voidType, "hello3", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);
ctMethod3.setModifiers(Modifier.PUBLIC);
ctMethod3.setBody("this.value = $1 + $2;");
ctMethod3.insertBefore("System.out.println(\"我在前面插入了:\"+$1);");
ctMethod3.insertAfter("System.out.println(\"我在最后插入了:\"+$1);");
ctClass.addMethod(ctMethod3);
结果:
0
参考:https://www.cnblogs.com/rickiyang/p/11336268.html
原文地址:https://www.cnblogs.com/duanxz/p/15664147.html