Java高级用法,写个代理侵入你 ?

小王是一个刚来不久的妹子 , 啊呸 , 是一个刚来不久的程序媛 , 经常垂头丧气的~让我很是不解 , 终于有一天我怕小王哪天想不开离职了岂不是会增加我的工作量(部门为数不多的妹子 - 1)?于是乎 , 我主动找小王进行了谈心找到了问题所在 , 原来是小王编程经验不足 , 不知道如何巧妙的进行日志打印 , 那么因果关系就总结出来了:经验不足导致编码经常出错 , 编码出错由于日志未打印导致排查困难 , 排查困难导致开发抑郁 。查到问题的原因 , 那么进行对症下药即可~
其实以上问题我相信很多小伙伴都遇到过 , 开发过程中未出现的错误在上线后就频频出现 , 那么只能不断的进行添加日志打印然后再打包上传进行问题跟踪 , 一天的时间绝大部分都浪费在了打包上传的上面 。那么能不能直接进行bug跟踪 , 然后查看到问题出错的所在?这种需求不亚于给奔跑中的汽车更换轮胎 , 匪夷所思却又无可奈何~其实有开发经验的小伙伴已经想出来一个中间件 , 那就是 Arthas!但是这篇文章不是介绍如何使用 Archas , 而是我们自己能不能实现这种动态调试的技能?那么就进入我们今天的整体 --- JAVA Agent 技术
Java Instrument这个玩意并不是什么 Java 的新特性 , 早在 JDK 1.5 的时候就诞生了 , 位于
java.lang.instrument.Instrumentation 中 , 它的作用就是用来在运行的时候重新加载某个类的 calss 文件的 api 。
这种类的实现方式其实是一种 Java Agent 技术 , 我们这里可以顺带了解一下什么是 Java Agent 。
一、Java Agent代理这个词对于我们开发人员来说并不默认 , 我们经常用到的 AOP 面向切面编程用到的就是代理方式 。它可以动态切入某个面 , 进行代码增强。这种不用重复补充轮子的方式大大增加了我们开发效率 , 那么这里捕获到了一个关键词 动态 。那么 Java Agent 如何实现?那就可以说到 JVMTI(JVM Tool Interface)  , 这是Java 虚拟机对外提供的 Native 编程接口 , 通过它我们可以获取运行时JVM的诸多信息 , 而 Agent 是一个运行在目标 JVM 的特定程序 , 它可以从目标 JVM 获取数据 , 然后将数据传递给外部进程 , 然后外部进程可以根据获取到的数据进行动态Enhance 。

Java高级用法,写个代理侵入你 ?

文章插图
 
那么 Java Agent 什么时候能够加载?
  • 目标 JVM 启动时
  • 目标 JVM 运行时
那么我们关注的是 运行时  , 这样子就能满足我们动态加载的需求 。
而 Java Agent看上去这么高大上 , 我们要如何编写?当然在 JDK 1.5 之前 , 实现起来是具有困难性的 , 我们需要编写 Native 代码来实现 , 那么 JDK 1.5 之后我们就可以利用上面说到的 Java Instrument 来实现了!
首先我们先了解一下 Instrumentation 这个接口 , 其中有几个方法:
  • addTransformer(ClassFileTransformer transformer, boolean canRetransform)
加入一个转换器 Transformer  , 之后所有的目标类加载都会被 Transformer 拦截 , 可自定义实现 ClassFileTransformer 接口 , 重写该接口的唯一方法 transform() 方法 , 返回值是转换后的类字节码文件
  • retransformClasses(Class<?>... classes)
对 JVM 已经加载的类重新触发类加载 , 使用上面自定义的转换器进行处理 。该方法可以修改方法体 , 常量池和属性值 , 但不能新增、删除、重命名属性或方法 , 也不能修改方法的签名
  • redefineClasses(ClassDefinition... definitions)
此方法用于替换类的定义 , 而不引用现有类文件字节 。
  • getObjectSize(Object objectToSize)
获取一个对象的大小
  • AppendToBootstrapClassLoaderSearch(JarFile jarfile)
将一个 jar 文件添加到 bootstrap classload 的 classPath 中
  • getAllLoadedClasses()
获取当前被 JVM 加载的所有类对象
redefineClasses 和 retransformClasses 补充说明


推荐阅读