大家好,我是冰河~~

在实际工作过程中,我们可以通过对Java的字节码进行插桩,以便拦截我们需要拦截的类和方法,对这些类和方法进行改造或者直接动态生成相应的类来实现拦截的逻辑。

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

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

相关的案例程序代码可以关注公众号:冰河技术 获取,也可以直接到Github和Gitee获取。

Github:https://github.com/sunshinelyz/bytecode

Gitee:https://gitee.com/binghe001/bytecode

注:本文的源代码对应着 bytecode-javassist-03 的程序源代码。

开发环境

  • JDK 1.8
  • IDEA 2018.03
  • Maven 3.6.0

Maven依赖

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

<properties><javassist.version>3.20.0-GA</javassist.version>
</properties><dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>${javassist.version}</version></dependency>
</dependencies>

案例效果

整体案例的效果比较简单,就是通过运行我们写的程序,能够动态生成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{//使用默认的ClassPoolClassPool pool = ClassPool.getDefault();//1.创建一个空类CtClass ctClass = pool.makeClass("io.binghe.bytecode.javassist.bean.User");//2.新增一个字段 private String name; 字段的名称为nameCtField param = new CtField(pool.get("java.lang.String"), "name", ctClass);//设置访问修饰符为privateparam.setModifiers(Modifier.PRIVATE);//设置字段的初始值为binghectClass.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搞起来吧。

附录

文中涉及到了Javassist中方法内部的引用变量$0$1 , 在Javassist中,还有一些其他的方法内部引用变量,冰河将其进行了总结,以方便大家学习。

好了,今天就到这儿吧,我是冰河,我们下期见~~

写在最后

如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

  • 《实践出真知:全网最强秒杀系统架构解密,不是所有的秒杀都是秒杀!!》
  • 《从零到上亿用户,我是如何一步步优化MySQL数据库的?(建议收藏)》
  • 《我用多线程进一步优化了亿级流量电商业务下的海量数据校对系统,性能再次提升了200%!!(全程干货,建议收藏)》
  • 《我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%!!(全程干货,建议收藏)》
  • 《我用10张图总结出了这份并发编程最佳学习路线!!(建议收藏)》
  • 《高并发场景下一种比读写锁更快的锁,看完我彻底折服了!!(建议收藏)》
  • 《全网最全性能优化总结!!(冰河吐血整理,建议收藏)》
  • 《三天撸完了MyBatis,各位随便问!!(冰河吐血整理,建议收藏)》
  • 《奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些并发编程知识是你必须要掌握的!完整学习路线!!(建议收藏)》
  • 《奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些核心技能是你必须要掌握的!完整学习路线!!(建议收藏)》
  • 《奉劝那些刚参加工作的学弟学妹们:这些计算机与操作系统基础知识越早知道越好!万字长文太顶了!!(建议收藏)》
  • 《我用三天时间开发了一款老少皆宜的国民级游戏,支持播放音乐,现开放完整源代码和注释(建议收藏)!!》
  • 《我是全网最硬核的高并发编程作者,CSDN最值得关注的博主,大家同意吗?(建议收藏)》
  • 《毕业五年,从月薪3000到年薪百万,我掌握了哪些核心技能?(建议收藏)》
  • 《我入侵了隔壁妹子的Wifi,发现。。。(全程实战干货,建议收藏)》
  • 《千万不要轻易尝试“熊猫烧香”,这不,我后悔了!》
  • 《清明节偷偷训练“熊猫烧香”,结果我的电脑为熊猫“献身了”!》
  • 《7.3万字肝爆Java8新特性,我不信你能看完!(建议收藏)》
  • 《在业务高峰期拔掉服务器电源是一种怎样的体验?》
  • 《全网最全Linux命令总结!!(史上最全,建议收藏)》
  • 《用Python写了个工具,完美破解了MySQL!!(建议收藏)》
  • 《SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)》
  • 《MySQL 8中新增的这三大索引,直接让MySQL起飞了,你竟然还不知道!!(建议收藏)》
  • 《撸完Spring源码,我开源了这个分布式缓存框架!!(建议收藏)》
  • 《亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)》
  • 《奉劝那些刚参加工作的学弟学妹们:要想学好并发编程,这些并发容器的坑是你必须要注意的!!(建议收藏)》
  • 《公司的报表工具太难用,我三天撸了个Excel工具,运营小姐姐直呼太好用了,现已开源!!(建议收藏)》
  • 《奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些并发编程核心技能是你必须要掌握的!!(建议收藏)》
  • 《阿里面试官:高并发大流量秒杀系统如何正确的解决库存超卖问题?(建议收藏)》
  • 《Redis五大数据类型与使用场景汇总!!(含完整实战案例,建议收藏)》

好了,今天就到这儿吧,小伙伴们点赞、收藏、评论,一键三连走起呀,我是冰河,我们下期见~~

字节码编程 | 使用Javassist生成JavaBean相关推荐

  1. 字节码编程 | 使用Javassist动态生成Hello World

    大家好,我是冰河~~ 字节码编程在实际的业务开发(CRUD)中并不常用,但是随着网络编程,RPC.动态字节码增强技术和自动化测试以及零侵入APM监控的不断发展与大量使用,越来越多的技术需要使用到字节码 ...

  2. 字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 通过前面两篇 javassist 的基本内容,大体介绍了:类池(ClassPool) ...

  3. 字节码编程,Byte-buddy篇二《监控方法执行耗时动态获取出入参类型和值》

    作者:小傅哥 博客:https://bugstack.cn - 汇总系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 案例是剥去外衣包装展示出核心功能的最佳学习方式! 就像是我们研 ...

  4. 牛散村:Java字节码编程javassist的详细介绍

    本篇文章将和大家分享一下关于Java字节码编程中一个非常之好用的技术javassist,下面将详细为大家介绍一下javassist技术,以及具体实例代码讲解. 一.Javassist入门 (一)Jav ...

  5. aop 获取方法入参出参_ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称及入参和出参结果并记录方法耗时...

    作者:小傅哥 博客:bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获! ❞ 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了 ...

  6. 字节码编程 | 工作多年的你依然重复做着CRUD?是否接触过这种技术?

    大家好,我是冰河~~ 最近和不少小伙伴聊天,发现大部分小伙伴,其中可能就包括正在看文章的你和我,工作时间已经不短了,有些小伙伴工作3~5年了,有些甚至超过8年了. 但是大部分小伙伴平时的工作都是在简单 ...

  7. ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称以及入参和出参结果并记录方法耗时

    作者:小傅哥 博客:bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了多少代码 ...

  8. ASM字节码编程 | 用字节码增强技术给所有方法加上TryCatch捕获异常并输出

    作者:小傅哥 博客:https://bugstack.cn Wiki:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有所收 ...

  9. Java字节码编程之非常好用的javassist

    我为什么要研究这个? 因为我在开发一个框架的时候需要用到. 我开发的这个框架,有一个注解,当用户输入变量名,类名的时候,我这个框架可以为其自动生成一个对象,并加载到内存中供以后使用. 这个小功能可费尽 ...

  10. java修改字节码技术,Javassist修改class,ASM修改class

    背景: 项目使用的Logback 1.1.11版本的类ch.qos.logback.core.rolling.helper.RollingCalendar的periodBarriersCrossed方 ...

最新文章

  1. 每秒上千订单场景下的分布式锁高并发优化实践!
  2. asp.net core系列 38 WebAPI 返回类型与响应格式--必备
  3. 计算机组成原理1--原码、反码、补码、移码之间的关系
  4. 压铸行业龙头广东鸿图携手用友U9实现数智化升级
  5. USTC English Club Note20171019(2)
  6. 20211004 矩阵的子空间
  7. 【算法】剑指 Offer 48. 最长不含重复字符的子字符串
  8. php秒杀框架,yii框架redis结合php实现秒杀效果(实例代码)
  9. JavaScript 验证 API中的setCustomValidity()方法
  10. 弹出打开蓝牙_iOS13隐藏特性 双蓝牙音频共享功能详解
  11. 华为手机android是什么意思,华为手机里的文件夹表示什么意思?
  12. 大学英语综合教程四 Unit 1至Unit 8 课文内容英译中 中英翻译
  13. 2019-05-23 嗅探工具;影音嗅探;IRIS嗅探器;
  14. figma对比sketch有什么优势和不足?
  15. 魔百盒cm211-1_ZG-晶晨S905和CH-晶晨S905L3B线刷-刷机固件及教程
  16. atl常量暴露的最简便方法
  17. ESP32学习11:PWM
  18. Python基础 实例
  19. 退出计算机二级培训的申请书,退社团申请书范文3000
  20. Cadence 17.4 原理图导出PDF

热门文章

  1. AppStore下载安装失败
  2. 黑苹果appstore下载软件报错,不能下载解决方法
  3. 上海市第三届上海市青少年算法竞赛(小学组)线上同步赛 数洞洞
  4. 打表法判断素数 c语言,素数打表(4种方法)
  5. Friendster,linkedin,orkut,liring对SNS的求索
  6. 微积分(第二版) 吴传生 编|高等教育出版 课后习题答案
  7. SIM-MICRO-SIM- NANO SIM 区别
  8. 【18】processing-声音(中文)
  9. Mac安装Xcode
  10. 4-18快速生成get和set方法、格式化代码、数组的非空验证、订餐系统案例