4修改初始值_使用Javassist动态修改类字节码竟然如此简单

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类(包括接口和枚举)。Javaassist 是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加或者是修改已有的方法,也可以 生成一个新的 .class类对象。难能可贵的是Javassist使用起来非常简单,不需要对Java字节码有深入的了解,就能快速上手。 本文演示使用的Javassist GAV信息:
<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文件。

36eb5287544ccddca065ba1cdf57ae50.png

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

5d43408878189a9c670b66433f0dbb18.png

不出所料,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();        }    }

bf3d78470e6b6cd4de0cc52c0af2b8fb.png

为了调用方法方便,我们还可以给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();}

a5f8d1ec45110c734ceb7089d7cf30ee.png

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);    }

7aee1ead05e26a06f3c3ffad8f1ed7a8.png

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, "王二麻子");    }

6bc1c7f3551e90a9bab89f44c229b6ad.png

今天我们主要测试的是使用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 对象,表示当前正在修改的类
参考文档:http://www.javassist.org/tutorial/tutorial2.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值