服务热线:站内信联系
         

百事3平台账号登录:字节码编程丨使用Javassist生成JavaBean

时间:2021-11-30 13:15:36 文章作者:百事3平台账号登录 点击:

这种方式几乎不需要修改源程序就能够达到我们想要的效果。今天,我们就一起使用Javassist来动态生成JavaBean对象。

掌握这个知识点后以便后续我们在手撸DAPM(分布式性能管理系统)时能够动态生成JavaBean对象来反序列化客户端发送的数据,或者从服务端响应回来的数据。

开发环境JDK 1.8IDEA 2018.03Maven 3.6.0Maven依赖

在项目的pom.xml文件中添加如下环境依赖。


    3.20.0-GA



    
        org.javassist
        javassist
        ${javassist.version}
    

案例效果

整体案例的效果比较简单,就是通过运行我们写的程序,能够动态生成User类的class字节码。如下所示。

package io.binghe.bytecode.javassist.bean;

public class User {
    private String name = "binghe";

    public User() {
        this.name = "binghe";
    }

    public User(String var1) {
        this.name = var1;
    }

    public void setName(String var1) {
        this.name = var1;
    }

    public String getName() {
        return this.name;
    }

    public void printName() {
        System.out.println(this.name);
    }
}
在这个User类中,有一个成员变量name,默认值为binghe。分别有一个无参构造方法和有参构造方法。成员变量name的get/set方法。打印成员变量name的方法printName()。

了解完案例的效果后,我们就开始动手实现如何动态生成这个User类。

案例实现

具体的案例实现,我们可以参考案例的效果一步步完成,这里,我们可以将整个User类的动态生成过程分为6个步骤,分别为:

创建User类。添加name字段。添加无参构造方法。添加有参构造方法。添加get/set方法。添加printName()方法。

好了,说干就干,接下来就按照这5个步骤动态生成User类。

创建User类
//使用默认的ClassPool
ClassPool pool = ClassPool.getDefault();

//1.创建一个空类
CtClass ctClass = pool.makeClass("io.binghe.bytecode.javassist.bean.User");

User类的创建方法和我们之前创建HelloWorld的类是相同的,首先是获取一个ClassPool对象,通过调用ClassPool对象的makeClass方法创建User类。

添加name字段
//2.新增一个字段 private String name; 字段的名称为name
CtField param = new CtField(pool.get("java.lang.String"), "name", ctClass);
//设置访问修饰符为private
param.setModifiers(Modifier.PRIVATE);
//设置字段的初始值为binghe
ctClass.addField(param, CtField.Initializer.constant("binghe"));

为User类添加成员变量name时,使用了Javassist中的CtField类。这里,我们使用的CtField的构造方法的第一个参数是成员变量的类型,第二个参数是变量的名称,第三个字段表示将这个变量添加到哪个类。

创建完CtField对象param后,我们调用了param的setModifiers()方法设置访问修饰符,这里将其设置为private。

接下来,为成员变量name赋默认值binghe。上述代码生成的效果如下所示。

private String name = "binghe";
添加无参构造方法
//3.添加无参的构造函数
CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
constructor.setBody("{" +
                    " $0.name = "binghe"; " +
                    "}");
ctClass.addConstructor(constructor);

添加无参构造方法时,使用了Javassist中的CtConstructor类,第一个参数是动态生成的目标类的构造方法的参数类型数组,第二个参数表示将构造方法添加到哪个类中。

接下来,通过调用CtConstructor的setBody()方法设置无参构造方法的方法体。这里需要注意的是方法体中只有一行代码时,可以省略{}, 但是为了防止出错,冰河强烈建议无论方法是否只有一行代码,都不要省略 {}。

细心的小伙伴肯定会发现在方法体中通过$0引用了成员变量name,估计小伙伴们也猜到了这个 $0 是干啥的。没错,它在生成User类后会被编译成this。

在Javassist中,还会有一些其他具有特定含义的符号,这个我们在文章的最后统一说明。

这段代码的效果如下所示。

public User() {
    this.name = "binghe";
}

接下来,就是调用CtClass的addConstructor()方法为User类添加无参构造方法。

添加有参构造方法
//4.添加有参构造函数
constructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass);
constructor.setBody("{" +
                    "$0.name = $1;" +
                    "}");
ctClass.addConstructor(constructor);

添加有参构造方法的整体流程和添加无参构造方法的整体流程相同,只是在创建CtConstructor对象时,在CtConstructor的构造方法的第一个参数类型数组中使用pool.get("java.lang.String")添加了一个数组元素,表示生成的目标类的构造方法存在一个String类型的参数。

另外,在设置方法体时,使用了如下代码。

$0.name = $1;

表示将构造方法的第一个参数赋值给成员变量name。这里,$0 表示 this, $1 表示第一个参数,$2表示第二个参数,以此类推。

这段代码的效果如下所示。

public User(String var1) {
    this.name = var1;
}
添加get/set方法
//5.添加getter和setter方法
ctClass.addMethod(CtNewMethod.setter("setName", param));
ctClass.addMethod(CtNewMethod.getter("getName", param));

添加get/set方法就比较简单了,直接使用CtClass的addMethod()添加,使用CtNewMethod的setter()方法生成set方法,其中,第一个参数为生成的方法的名称setName,第二个参数表示是为哪个字段生成setName方法。

使用CtNewMethod的getter()方法生成get()方法,第一个参数为生成的方法的名称getName,第二个参数表示是为哪个字段生成getName方法。

这段代码的效果如下所示。

public void setName(String var1) {
    this.name = var1;
}

public String getName() {
    return this.name;
}
添加printName()方法
//6.创建一个输出name的方法
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{" +
        "System.out.println(name);" +
        "}");
ctClass.addMethod(ctMethod);

添加printName()方法使用了Javassist中的CtMethod类,创建CtMethod类的对象时,第一个参数为方法的返回类型,第二个参数为方法的名称printName,第三个参数为方法的参数类型数组,第四个参数表示将生成的方法添加到哪个类。

接下来,调用CtMethod的setModifiers()方法来设置printName()方法的访问修饰符,这里将其设置为public。紧接着为printName()方法设置方法体,在方法体中简单的在命令行打印成员变量name。

最后通过CtClass的addMethod()方法将生成的printName方法添加到User类中。

这段代码的效果如下所示。风暴平台登录

public void printName() {
    System.out.println(this.name);
}
完整案例

为了方便小伙伴们更加清晰的看到完整的源代码,这里我也将完整的源代码贴出来,如下所示。

/**
 * @author binghe (公众号:冰河技术)
 * @version 1.0.0
 * @description 使用Javassist生成一个User类, 并测试
 */
public class CreateUserClass {

    /**
     * 使用Javassist创建一个User对象
     */
    public static void createUser() throws Exception{
        //使用默认的ClassPool
        ClassPool pool = ClassPool.getDefault();

        //1.创建一个空类
        CtClass ctClass = pool.makeClass("io.binghe.bytecode.javassist.bean.User");

        //2.新增一个字段 private String name; 字段的名称为name
        CtField param = new CtField(pool.get("java.lang.String"), "name", ctClass);
        //设置访问修饰符为private
        param.setModifiers(Modifier.PRIVATE);
        //设置字段的初始值为binghe
        ctClass.addField(param, CtField.Initializer.constant("binghe"));

        //3.添加无参的构造函数
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, ctClass);
        constructor.setBody("{" +
                " $0.name = "binghe"; " +
                "}");
        ctClass.addConstructor(constructor);

        //4.添加有参构造函数
        constructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass);
        constructor.setBody("{" +
                "$0.name = $1;" +
                "}");
        ctClass.addConstructor(constructor);

        //5.添加getter和setter方法
        ctClass.addMethod(CtNewMethod.setter("setName", param));
        ctClass.addMethod(CtNewMethod.getter("getName", param));

        //6.创建一个输出name的方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, ctClass);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{" +
                "System.out.println(name);" +
                "}");
        ctClass.addMethod(ctMethod);

        ctClass.writeFile();
    }
}
效果演示

编写main方法,直接调用CreateUserClass类的createUser()方法,如下所示。

public static void main(String[] args) throws Exception {
    CreateUserClass.createUser();
}

运行main()方法后,生成了我们想要的User类的字节码,如下所示。

字节码编程丨使用Javassist生成JavaBean


效果符合我们的预期。

案例总结

我们使用Javassist动态生成了符合预期的User类对象,通过本文的学习,我们掌握了如何使用Javassist生成JavaBean对象。是不是很简单呢?小伙伴们赶紧打开IDEA搞起来吧。


本文由百事3平台【官方首页】编辑发布,转载请注明出处

本文链接:http://altontobey.org/article/baishi3yulexinwen/98.html

【产品推荐】