一、字节码是什么
Java程序都是跑在JVM上的,我们日常所编写的 java文件需要先编译为.class文件然后才可以被类加载器加载后进入到JVM中,被正确识别后才能运行,而这个.class文件里的内容就是我们今天要说的字节码。
我们可以通过命令:javap -verbose + 类名 查看字节码内容,如下:
二、实现思路
我们先看一张流程图:
我们手工编写 Java 文件,然后编译成 .class文件,最终通过类加载器将类加载进 JVM。如果我们不想更改源码,但是又想对程序做一些修改,让程序按照我们预期去运行,那么我们可以在上图中的编译和加载这两个步骤去进行,即:如果我们可以把.class文件的内容改成我们所需要的,我们的预期就得以实现了。
三、应用场景
1、流量回放
我们可以在程序的入口处,修改入口处的字节码,增加流量进入时的存储逻辑,当流量进来时,就可以按照我们自己的格式和要求将流量落地,进而给后续的回放提供支撑.有些团队会选择在代码中加注解来识别,比如基于Spring的aop或cglib来达到这种效果,但是这样会对代码有一定的侵入性.个人感觉不如原生字节码注入好.
2、各类开关
我们经常会有一些需求比如线上线下环境要实现不同的逻辑处理,如调用第三方接口有验签或加密时,可能为了线下的mock方便就会加各种开关来关闭掉,此时其实我们就可以使用字节码技术来做到不修改源代码逻辑,随意控制开关的有无.
3、异常代码注入
现在有一个比较好玩的技术叫混沌工程,几年前有一些说法叫故障演练,在注入故障时,除了系统级的故障外,其实我们也可以模拟一些代码级的故障,比如各种异常返回,抛出异常,方法执行超时等等.那么这些随机的,各种异常的代码故障,都可以应用字节码修改的手段来完成.目前这块也有比较成熟的框架供使用,后续会有介绍
四、JVMTI
- JVMTI: JVM Tool Interface 是JVM提供的native编程接口,是JVM为我们提供的一套针对java程序的"后门"API,方便我们做字节码增强,剖析,调试,监控,分析线程等。
- Instrument: JVM提供的一个可以修改已加载类的接口,可以对java程序做agent和attach两种方式的修改。
- agent: 借助于Instrument相关api,在启动程序时指定本地一个jar包,在将.class文件加载进内存之后,运行main方法之前,完成相关增强任务.这种方式有一个问题就是每次想要完成增强都需要重新启动一下进程来完成增强任务,如果不想重新启动就完成修改,那么就需要使用第二种方式attach。
- attach: 与agent的区别是,此种方式不需要重新启动,可以对运行中的进程直接挂载来完成增强的任务,在attach成功之后,具体实现即对已经加载的class重新加载一次,来完成增强任务。
五、类库支持
当前主要有大神级的ASM, 以及大侠级的 javassist,其他也有一些,不过多是基于ASM做的各种二次开发和扩展来的。我们的演示将会以 javassist来展开,这款框架主要是简单,上手快,可以快速出结果。
六、agent演示