作者:董子龙
前言
前段时间一直想参照lombok的实现原理写一篇可以生成业务单据修改记录插件的专利,再查阅资料的过程中,偶然了解到了字节码增强工具-byteBuddy。但是由于当时时间紧促,所以没有深入的对该组件进行了解。其实再我们的日常开发中,字节码增强组件的身影无处不在,例如spring-aop和mybatis。本着知其然也要知其所以然的精神,我决定沉下心来,对字节码增强技术做一个深入的学习和总结,本文作为该系列的开篇,主要是对字节码做一下简单的介绍,为我们后面的深入学习打下一个好的基础。
一、字节码简述
字节码是一种中间状态的二进制文件,是由源码编译过来的,可读性没有源码的高。cpu并不能直接读取字节码,在java中,字节码需要经过JVM转译成机械码之后,cpu才能读取并运行。
使用字节码的好处:一处编译,到处运行。java就是典型的使用字节码作为中间语言,在一个地方编译了源码,拿着.class文件就可以在各种计算机运行。
二、字节码增强的使用场景
如果我们不想修改源码,但是又想加入新功能,让程序按照我们的预期去运行,可以通过编译过程和加载过程中去做相应的操作,简单来讲就是:将生成的.class文件修改或者替换称为我们需要的目标.class文件。
由于字节码增强可以在完全不侵入业务代码的情况下植入代码逻辑,所以可以用它来做一些酷酷的事,比如下面的几种常见场景:
1、动态代理
2、热部署
3、调用链跟踪埋点
4、动态插入log(性能监控)
5、测试代码覆盖率跟踪
...
三、字节码增强的实现方式
字节码工具 |
类创建 |
实现接口 |
方法调用 |
类扩展 |
父类方法调用 |
优点 |
缺点 |
常见使用 |
学习成本 |
java-proxy |
支持 |
支持 |
支持 |
不支持 |
不支持 |
简单动态代理首选 |
功能有限,不支持扩展 |
spring-aop,MyBatis |
1星 |
asm |
支持 |
支持 |
支持 |
支持 |
支持 |
任意字节码插入,几乎不受限制 |
学习难度大,编写代码多 |
cglib |
5星 |
javaassit |
支持 |
支持 |
支持 |
支持 |
支持 |
java原始语法,字符串形式插入,写入直观 |
不支持jdk1.5以上的语法,如泛型,增强for |
Fastjson,MyBatis |
2星 |
cglib |
支持 |
支持 |
支持 |
支持 |
支持 |
与bytebuddy看起来差不多 |
正在被bytebuddy淘汰 |
EasyMock,jackson-databind |
3星 |
bytebuddy |
支持 |
支持 |
支持 |
支持 |
支持 |
支持任意维度的拦截,可以获取原始类、方法,以及代理类和全部参数 |
不太直观,学习理解有些成本,API非常多 |
SkyWalking,Mockito,Hibernate,powermock |
3星 |
四、简单示例
AOP是我们在日常开发中常用的架构设计思想,AOP的主要的实现有cglib,Aspectj,Javassist,java proxy等。接下来,我们就以我们日常开发中会遇到的在方法执行前后打印日志为切入点,手动用字节码来实现一下AOP。
定义目标接口与实现
定义了类SayService,再执行say方法之前,我们会打印方法开始执行start,方法执行之后,我们会打印方法执行结束end
ASM实现AOP
4.1.1、引入jar包
4.1.2、AOP具体实现
4.1.3、测试类输出结果
Javassist实现AOP
4.2.1、引入jar包
4.2.2、AOP具体实现
4.2.3、测试类输出结果
五、总结
作为字节码增强系列文章的开篇,只是简单的介绍了一下字节码的定义、字节码的实现方式,最后通过具体示例向大家展示了如何对字节码进行增强。再后续的文章中,会对相关框架的原理及具体应用做一个细化的总结,欢迎各位大佬的批评与指正。