问题
最近工作上遇到一个需求:
- 有一个老系统,由许多模块组成,这些模块之间采用http的接口互相调用
- 现在需要了解各接口的调用情况,如调用时间、耗时、参数、返回值等
- 要求对原系统的改动越少越好
思考
面对这个需求,应该如何解决:
- 修改原系统的各模块,在调用接口的地方加代码
- 优点:简单直接,想怎么加就怎么加
- 缺点:需要对原系统的每个模块都进行改动,与需求3有很大的冲突
- 使用Spring AOP,通过配置的方式动态加代码
- 优点:实现比较简单,对原系统的改动也比较少
- 缺点:对没有采用Spring技术的模块就没办法了
有没有其他办法?
这时,想起了以前曾经了解过一些的java代理技术。这种技术能在运行时修改java代码,对原有的代码几乎不需要任何改造。
背景知识
- java代理能通过修改JVM中运行的Java应用的字节码,修改这些应用的行为
- java代理本身是一个特殊的java类
- java代理必须实现一个premain方法,作为代理的入口点
- java代理还可以实现一个agentmain方法,用于代理后启动(晚于应用启动)的场合
- java代理还必须在包里提供manifest,提供一些元数据(比如代理类的名字、是否可以修改类)
- 虚拟机通过-javaagent参数指定java代理
- 开发java代理需要对字节码进行操作,java标准库没有提供相应的API,好在社区开发了一些相关的库,如javassit和ASM
实操
了解过java代理的背景知识后,是时候开始实际操作了。这次我们将:
- 编写一个java代理
- 使用javassit库来修改HttpClient的代码,具体来说,是org.apache.http.impl.client.InternalHttpClient的doExecute方法,这可通过调查方法调用栈获得
- 实现在调用http接口时,输出参数、耗时等信息
确定依赖
- 项目需要对HttpClient进行修改,因此需依赖HttpClient,而HttpClient是目标模块本身就有的依赖,所以这里将scope设置为provided
- 项目需使用javassist进行字节码操作,因此需依赖javassist
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
<scope>provided</scope>
</dependency>
定义java代理类
首先需要定义java代理类。
public class Agent {
public stati