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


两者区别:
redefineClasses 是自己提供字节码文件替换掉已存在的 class 文件
retransformClasses 是在已存在的字节码文件上修改后再进行替换
替换后生效的时机
如果一个被修改的方法已经在栈帧中存在 , 则栈帧中的方法会继续使用旧字节码运行 , 新字节码会在新栈帧中运行
注意点
两个方法都是只能改变类的方法体、常量池和属性值 , 但不能新增、删除、重命名属性或方法 , 也不能修改方法的签名
二、实现 Agent1、编写方法上面我们已经说到了有两处地方可以进行 Java Agent 的加载 , 分别是 目标JVM启动时加载 和 目标JVM运行时加载 , 这两种不同的加载模式使用不同的入口函数:
1、JVM 启动时加载
入口函数如下所示:
// 函数1public static void premain(String agentArgs, Instrumentation inst);// 函数2public static void premain(String agentArgs);JVM 首先寻找函数1 , 如果没有发现函数1 , 则会寻找函数2
2、JVM 运行时加载
入口函数如下所示:
// 函数1public static void agentmain(String agentArgs, Instrumentation inst);// 函数2public static void agentmain(String agentArgs);与上述一致 , JVM 首先寻找函数1 , 如果没有发现函数1 , 则会寻找函数2
这两组方法的第一个参数 agentArgs 是随同 “-javaagent” 一起传入的程序参数 , 如果这个字符串代表了多个参数 , 就需要自己解析这参数 , inst 是 Instrumentation 类型的对象 , 是 JVM 自己传入的 , 我们可以那这个参数进行参数的增强操作 。

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

文章插图
 
2、声明方法当定义完这两组方法后 , 要使之生效还需要手动声明 , 声明方式有两种:
1、使用 MANIFEST.MF 文件
我们需要创建
resources/META-INF.MANIFEST.MF 文件 , 当 jar包打包时将文件一并打包 , 文件内容如下:
Manifest-Version: 1.0Can-Redefine-Classes: true# true表示能重定义此代理所需的类 , 默认值为 false(可选)Can-Retransform-Classes: true# true 表示能重转换此代理所需的类 , 默认值为 false (可选)Premain-Class: cbuc.life.agent.MainAgentDemo#premain方法所在类的位置Agentmain-Class: cbuc.life.agent.MainAgentDemo#agentmain方法所在类的位置2、如果是maven项目 , 在pom.xml加入
Java高级用法,写个代理侵入你 ?

文章插图
 
3、指定 agent要让目标JVM认你这个 Agent  , 你就要给目标JVM介绍这个 Agent
1、JVM 启动时加载
我们直接在 JVM 启动参数中加入 -javaagent 参数并指定 jar 文件的位置
# 将该类编译成 class 文件javac TargetJvm.java# 指定agent程序并运行该类java -javaagent:./java-agent.jar TargetJvm2、JVM 运行时加载
要实现动态调试 , 我们就不能将目标JVM停机后再重新启动 , 这不符合我们的初衷 , 因此我们可以使用 JDK 的 Attach Api 来实现运行时挂载 Agent 。
Attach Api 是 SUN 公司提供的一套扩展 API , 用来向目标 JVM 附着(attach)在目标程序上 , 有了它我们可以很方便地监控一个 JVM 。Attach Api 对应的代码位于 com.sun.tools.attach包下 , 提供的功能也非常简单:
  • 列出当前所有的 JVM 实例描述
  • Attach 到其中一个 JVM 上 , 建立通信管道
  • 让目标JVM加载Agent
该包下有一个类 Virtualmachine , 它提供了两个重要的方法:
  • VirtualMachine attach(String var0)
传递一个进程号 , 返回目标 JVM 进程的 vm 对象 , 该方法是 JVM进程之间指令传递的桥梁 , 底层是通过 socket 进行通信
  • void loadAgent(String var1)
该方法允许我们将 agent 对应的 jar 文件地址作为参数传递给目标 JVM , 目标 JVM 收到该命令后会加载这个 Agent
有了 Attach Api  , 我们就可以创建一个java进程 , 用它attach到对应的jvm , 并加载agent 。
以下是简单的 Attach 代码实现:
Java高级用法,写个代理侵入你 ?

文章插图
 


推荐阅读