字节码编程丨使用Javassist生成JavaBean
这种方式几乎不需要修改源程序就能够达到我们想要的效果。今天,我们就一起使用Javassist来动态生成JavaBean对象。
掌握这个知识点后以便后续我们在手撸DAPM(分布式性能管理系统)时能够动态生成JavaBean对象来反序列化客户端发送的数据,或者从服务端响应回来的数据。 开发环境JDK 1.8 IDEA 2018.03 Maven 3.6.0 Maven依赖
在项目的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动态生成了符合预期的User类对象,通过本文的学习,我们掌握了如何使用Javassist生成JavaBean对象。是不是很简单呢?小伙伴们赶紧打开IDEA搞起来吧。
奥特曼赛罗形态盘点赛罗奥特曼是赛文奥特曼的亲生儿子,但赛文并没有告诉赛罗自己是赛罗的亲生父亲。赛罗曾经为了证明自己的实力而意图独占等离子火花塔的能量(等离子火花核心),但被赛文阻止,后托付给雷欧。为
十大远古海洋巨兽沧龙中生代海洋中最大的顶级掠食者。虽然它的历史很短(从陆地上的崖蜥进化而来,在白垩纪中晚期才出现并且迅速繁衍,随后和恐龙一起灭绝),但却一路平步青云,把比它历史早远得多的海洋爬行动
奥特曼幽蓝魅影托雷基亚奥特曼托雷基亚这个名字在光之国意为癫狂的好奇心,揭示了他为追寻真理却最终陷入黑暗而疯狂的不归路。托雷基亚奥特曼曾经是光之国的科学家,是希卡利奥特曼的下级。是泰罗奥特曼的朋友,后来在追求善
世界十大猛犬猛犬霸主中国藏獒藏獒,又名西藏獒獒犬番狗龙狗。原产于中国青藏高原,是一种高大凶猛垂耳的猛犬。身长约120厘米左右,体毛粗硬,丰厚,外层披毛不太长,底毛在寒冷的气候条件下,则浓密且软
九头蛇许德拉希腊神话中的怪物九头蛇是一种传说中有九个头的大蛇在许多文化中都存在。欧洲传统中的九头蛇名为希腊语,英语Hydra(译为海德拉,意为水蛇)。传说生物,在古希腊神话里出现最为频繁,另外波斯古经圣经非洲
古代十大名曲中华古韵,向有中国十大名曲一说。分别为高山流水广陵散平沙落雁梅花三弄十面埋伏夕阳箫鼓渔樵问答胡笳十八拍汉宫秋月和阳春白雪。据专家考证,这些古代中国名曲的原始乐谱大都失传,今天流传的
数码宝贝盘点皇家骑士奥米加兽形态及亚种病毒克星的战斗暴龙兽及金属加鲁鲁兽,因期待善行的人们的强大意志而融合,诞生出皇家骑士其中一员的圣骑士型数码兽。兼具两者特性的数码兽,是无论任何状况下都能充分发挥自身能力的复合型战士
数码宝贝暴龙兽系列数码兽暴龙兽等级成熟期类型恐龙型属性疫苗种头部的皮肤硬化,覆盖着像甲虫一样的壳的恐龙型数码兽。拥有锐利的爪巨大的角等如全身凶器般的身体,是非常有攻击性的数码兽。但是,智力很高,如果能驯服
捷德奥特曼的史上最强之敌终极审判者吉尔巴里斯克西亚人为了使宇宙获得永恒的和平而制造的人工头脑,是君临拥有大量加拉特隆的吉尔巴利斯军团的顶端者的存在。在还是未完全体的时候是类似于圆筒柱型的电脑,不具备任何攻击力,曾在被泰罗追击
还记得数码I中保护过美美和嘉儿的鼻涕兽吗?居然有究极体鼻涕兽鼻涕兽有着像蛞蝓(注俗称鼻涕虫)一样身体的软体型数码兽。喜欢阴暗潮湿的地方,又不厉害的软体型成熟期数码宝贝,通常用自己的大便来当武器,绝招是便便投射。绝招不具有杀伤力,但是却很恶心
数码宝贝天女兽的女神进化路线天女兽拥有美丽女性身姿的大天使型数码兽。以前被分类为天使型,但因为其高能力而被明确是大天使型。特征就是成熟期的天使拥有六片羽翼,而完全体的天使则拥有八片羽翼。性格相当温和,但是她绝