注意:在mac上安装了的jdk是能直接找到 VirtualMachine 类的 , 但是在windows中安装的jdk无法找到 , 如果你遇到这种情况 , 请手动将你jdk安装目录下:lib目录中的tools.jar添加进当前工程的Libraries中 。上面代码十分简易的实现了 Attach 的方式 , 通过寻找当前系统中所有运行的 JVM 进程 , 然后通过比对 PID 来筛选出目标JVM , 然后让 Agent 附着在目标 JVM 上 。当然这边已经简易到直接在代码中指定目标JVM的 PID , 这种方式在实际生产中是十分不可取的 , 我们可以通过动态参数的方式传入 PID~!而 Attach 的执行原理也不复杂 , 简单流程如下:
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q9361516-4.jpg)
文章插图
三、案例说明我们上述简单聊了下 Java Agent 的实现过程 , 那我们下面也简单写个案例来理解一下 Java Agent 的实现过程~
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q9363494-5.jpg)
文章插图
我们上面说到可以使用 Java Instrumentation 来完成动态类修改的功能 , 并且在 Instrumentation 接口中我们可以通过 addTransformer() 方法来增加一个类转换器 , 类转换器由类 ClassFileTransformer 接口实现 。该接口中有一个唯一的方法 transform() 用于实现类的转换 , 也就是我们可以增强类处理的地方!当类被加载的时候就会调用 transform()方法 , 实现对类加载的事件进行拦截并返回转换后新的字节码 , 通过 redefineClasses()或retransformClasses()都可以触发类的重新加载事件 。
实际操作1)准备目标JVM我们这里直接使用一个 SpringBoot 项目来试验 , 方便大家增强改造~ 项目结构如下:
target-jvm├─src├─main├─java└─cbuc└─life└─targetjvm├─controller|└─TestController.java└─service|└─SimpleService.java└─TargetJvmApplication.java
其中 TestController 和 SimpleService 两个类的内容也很简单 , 直接贴代码![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q9362536-6.jpg)
文章插图
【Java高级用法,写个代理侵入你 ?】
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q93CU8-7.jpg)
文章插图
2)准备 Agent1、编写方法然后编写我们的Agent jar包 。因为懒惰 , 所以我这边将 premain 和 agentmain 两个方法写在同一个 jar 包中 , 然后分别以 启动时 和 运行时 来模拟场景~
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q9363B4-8.jpg)
文章插图
很简单 , 一个类中包含了我们需要的所有功能~ 防止图片内容过于拥挤 , 小菜贴心地分别粘贴出核心代码:
- premain
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q93A2V-9.jpg)
文章插图
- agentmain
![Java高级用法,写个代理侵入你 ?](http://img.jiangsulong.com/220502/0Q93Ba2-10.jpg)
文章插图
- ClassFileTransformer
然后运行mvn assembly:assembly 既可
3)启动 Agent当我们已经准备好了两个 jar 包便可以开始测试了!
1、启动时加载
nohup java -javaagent:./java-agent-jar-with-dependencies.jar -jar target-jvm.jar &
xxxxxxxxxxbr nohup java -javaagent:./java-agent-jar-with-dependencies.jar -jar target-jvm.jar &
我们直接启动时添加参数 , 带上我们的 Agent jar包结果并没有让小菜太尴尬 , 成功的实现我们想要的功能 , 但是这只是启动时加载 , 明显不是我们想要的~ 我们来试下运行时如何加载
2、运行时加载正常运行下 , 方法并没有做耗时统计 , 我们的需求就来了 , 我们想要统计该方法的耗时 , 首先获取该进程ID
然后通过 Attach 方式(调用controller 的 active() 方法)附着 Agent , 我们可以实时查看控制台
已经可以看到 Agent 似乎已经成功附着了 , 然后我们继续请求 test 接口
可以发现 resolve 方法已经被我们增强了!
四、题外话上面我们已经简单的实现了动态操作目标类文件 , 文章开头就说明了给奔跑中的汽车更换轮胎是一个匪夷所思却又无可奈何的需求 , 但是这个需求能不能让别人实现 , 其实是可以的 , 而这个就是小菜的主要目的 , 我们了解了如何实现动态换轮胎的原理后 , 当我们运用其成熟的中间件也能更加应手而不会不知所措 , 知识不能让我们只学会卧槽两个字 , 而是当别人实现的时候我们能默默思考 , 思考后再说出牛逼~!感兴趣的同学不妨拉取一下源码演练一番:Arthas gitee , 已经使用过类似 Arthas 或 BTrace 的同学 , 看完相信会更加了解其工作运行原理 , 没使用过的同学下次用到的时候也不会那么战战兢兢!
推荐阅读
- 阿里架构师整理的 Netty 学习笔记之:Java NIO 网络编程
- Javascript的New、Apply、Bind、Call知多少
- 莳萝子在香肠中的用量,莳萝在烹调中的作用及用法
- 什么是Java中的反应性流?
- 莳萝对皮肤的作用,莳萝在烹调中的作用及用法
- 快速了解JavaScript的DOM模型
- Java 文件操作必知概念
- java开发框架之SSM整合框架
- 明日草的功效与作用和用法,明日叶的功效
- 普洱茶花式饮用法,水煎普洱茶的功效