没有代理的检测 Java 字节码

问题描述

是否可以在没有 Java 代理的情况下动态检测 Java 字节码?我之前使用 Java 代理检测过字节码,做了类似这样的事情:

ClassFileTransformer myTransformer = new Transformer();
instrument.addTransformer(myTransformer,true);
instrument.retransformClasses(classInstance);
instrument.removeTransformer(myTransformer);

但是这是否可能不使用 Java 代理?我想要做的是调用一个方法,该方法将在 JVM 运行后的任何给定时间执行我的检测,而无需使用代理。

解决方法

在没有 Instrumentation 实现实例的情况下执行字节码转换的唯一方法是

  • 自定义类加载器,可以在调用 defineClass 之前更改字节(仅限于通过该加载器加载的类)

  • 甚至在类加载之前使用修改后的字节调用 MethodHandles.Lookup.defineClass,这适用于具有延迟加载的广泛 JVM,但仅限于您自己的模块或对您的模块打开的模块

这两种方法都不能改变已经加载的类。这需要 Instrumentation 引用,并且 JVM 分发此类引用的唯一地方是 Java 代理的初始化方法。因此要使用它,Java 代理是不可避免的,即使它可能只是一个存储引用的存根,供您的应用程序代码使用。

请注意,从 Java 9 开始,jar 文件有 Launcher-Agent-Class 清单属性,可以指定在使用 Main-Class 指定的类启动之前启动的 Java 代理的类。这样,您可以轻松地让代理与 JVM 中的应用程序代码协作,而无需任何其他命令行选项。代理可以像在主类中使用 agentmain 方法一样简单,将 Instrumentation 引用存储在静态变量中。

the java.lang.instrument package documentation...

在 JVM 尚未通过代理启动的情况下使用 Instrumentation 实例比较棘手。它必须支持在启动后启动代理,例如通过附加 API。 This answer 在其结尾处展示了这样一种自我附加以获取 Instrumentation。当您的应用程序 jar 文件中有必要的清单属性时,您甚至可以将其用作代理 jar 并省略临时存根文件的创建。

然而,最近的 JVM 禁止自附加,除非在启动时指定了 -Djdk.attach.allowAttachSelf=true,但我想,在启动时采取额外的步骤,正是您不想做的。规避这一点的一种方法是使用另一个过程。这个过程要做的就是附加到您的原始进程并告诉 JVM 启动代理。然后,它可能已经终止,其他一切都与引入此限制之前相同。

this comment 中所述,Byte-Buddy 已经实现了那些必要的步骤,而精简的 Byte-Buddy-Agent 仅包含该逻辑,因此您可以使用它在其上构建自己的逻辑。