<dependency>    <groupId>org.javassistgroupId>    <artifactId>javassistartifactId>    <version>3.25.0-GAversion>dependency>


1. 创建类对象


为了演示方便,我们先来创建一个全新的 .class类对象。

@Test    public void test() {        try {            ClassPool pool = ClassPool.getDefault();            // 1. 创建一个空类            CtClass cc = pool.makeClass("com.github.jimbean0615.test.Person");            // 2. 新增一个字段 private String name;            // 字段名为name            CtField param = new CtField(pool.get("java.lang.String"), "name", cc);            // 访问级别是 private            param.setModifiers(Modifier.PRIVATE);            // 初始值是zhangsan            cc.addField(param, CtField.Initializer.constant("zhangsan"));            // 3. 生成 getter/setter 方法            cc.addMethod(CtNewMethod.setter("setName", param));            cc.addMethod(CtNewMethod.getter("getName", param));            // 4. 添加无参的构造函数            CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);            cons.setBody("{name = \"zhangsan\";}");            cc.addConstructor(cons);            // 5. 添加有参的构造函数            cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);            // $0代表this            // $1,$2,$3... 依次代表第几个方法参数            // $$表示所有参数            cons.setBody("{$0.name = $1;}");            cc.addConstructor(cons);            // 6. 创建一个名为printName方法,无参数,无返回值,输出name值            CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);            ctMethod.setModifiers(Modifier.PUBLIC);            ctMethod.setBody("{System.out.println(name);}");            cc.addMethod(ctMethod);            //这里会将这个创建的类对象编译为.class文件            cc.writeFile("/Users/zhangjianbin/Downloads/java");        } catch (Exception e) {            e.printStackTrace();        }    }


不出意外的话,我们到 /Users/zhangjianbin/Downloads/java/com/github/jimbean0615/test目录下会看到新创建的Person.class文件。


java properties动态Set 类 javassist动态修改方法_Java

使用idea可以打开该class文件,看看内容是否符合预期。

java properties动态Set 类 javassist动态修改方法_Java_02


不出所料,class文件内容正是我们想要的。 2. 调用类方法


接下来我们开始尝试调用以上class文件的方法。

@Test    public void test1() {        try {            ClassPool pool = ClassPool.getDefault();            pool.appendClassPath("/Users/zhangjianbin/Downloads/java");            // 加载class文件            CtClass ctClass = pool.get("com.github.jimbean0615.test.Person");            // 创建类实例            Object person = ctClass.toClass().newInstance();            // 设置值            Method setName = person.getClass().getMethod("setName", String.class);            setName.invoke(person, "lisi");            // 输出值            Method execute = person.getClass().getMethod("printName");            execute.invoke(person);        } catch (Exception e) {            e.printStackTrace();        }    }

java properties动态Set 类 javassist动态修改方法_github_03


为了调用方法方便,我们还可以给Person.class类设置一个接口。

@Test    public void test2() throws Exception {        ClassPool pool = ClassPool.getDefault();        pool.appendClassPath("/Users/zhangjianbin/Downloads/java/");        pool.appendClassPath(new ClassClassPath(this.getClass()));        // 获取接口        CtClass codeClassI = pool.get("com.github.jimbean0615.test.PersonI");        // 获取上面生成的类        CtClass ctClass = pool.get("com.github.jimbean0615.test.Person");        // 使代码生成的类,实现 PersonI 接口        ctClass.setInterfaces(new CtClass[]{codeClassI});        // 以下通过接口直接调用 强转        PersonI person = (PersonI) ctClass.toClass().newInstance();        System.out.println(person.getName());        person.setName("王二麻子");        person.printName();    }
/** * @author zhangjb */public interface PersonI {    void setName(String name);    String getName();    void printName();}

java properties动态Set 类 javassist动态修改方法_4修改初始值_04


3. 修改现有类方法


一般实际工作中遇到的场景更多的是修改已有的类。比如常见的日志切面。利用Javassist来实现类似的功能。

@Test    public void test3() throws Exception {        ClassPool pool = ClassPool.getDefault();        pool.appendClassPath("/Users/zhangjianbin/Downloads/java/");        pool.appendClassPath(new ClassClassPath(this.getClass()));        // 获取生成的类        CtClass ctClass = pool.get("com.github.jimbean0615.test.Person");        CtMethod ctmethod = ctClass.getDeclaredMethod("printName");        ctmethod.insertBefore("{ $0.name = \"王二麻子\"; }");        // 以下通过接口直接调用 强转        Object person = ctClass.toClass().newInstance();        Method printNameMethod = person.getClass().getMethod("printName");        printNameMethod.invoke(person);    }

java properties动态Set 类 javassist动态修改方法_github_05

4. 新增一个方法

@Test    public void test4() throws Exception {        ClassPool pool = ClassPool.getDefault();        pool.appendClassPath("/Users/zhangjianbin/Downloads/java/");        pool.appendClassPath(new ClassClassPath(this.getClass()));        // 获取生成的类        CtClass ctClass = pool.get("com.github.jimbean0615.test.Person");        //新增一个方法        CtMethod ctMethod = new CtMethod(CtClass.voidType, "setPersonName", new CtClass[]{pool.get("java.lang.String")}, ctClass);        ctMethod.setModifiers(Modifier.PUBLIC);        ctMethod.setBody("{ System.out.println(\"the name after modified is \" + $1);}");        ctClass.addMethod(ctMethod);        // 以下通过接口直接调用 强转        Object person = ctClass.toClass().newInstance();        Method setPersonNameMethod = person.getClass().getMethod("setPersonName", String.class);        setPersonNameMethod.invoke(person, "王二麻子");    }

java properties动态Set 类 javassist动态修改方法_java_06


今天我们主要测试的是使用Javassist进行方法Method相关操作,对于属性Field的操作类似,这里就不做演示了。 附录:

Javassist 以 $开头的几个标识符的含义:


符号

含义

$0, $1, $2, ...

this and 方法的参数

$args

方法参数数组.它的类型为 Object[]

$$

所有实参。例如, m($$) 等价于 m($1,$2,...)

$cflow(...)

cflow 变量

$r

返回结果的类型,用于强制类型转换

$w

包装器类型,用于强制类型转换

$_

返回值

$sig

类型为 java.lang.Class 的参数类型数组

$type

一个 java.lang.Class 对象,表示返回值类型

$class

一个 java.lang.Class 对象,表示当前正在修改的类