Java agent是在另一个Java应用程序(“目标”应用程序)启动之前执行的Java程序,为该agent提供修改目标应用程序或其运行环境的机会。在本文中,我们将从基础知识开始,使用字节码操作工具Byte Buddy实现高级代理。
在最基本的用例中,Java agent设置应用程序属性或配置特定的环境状态,从而使代理能够充当可重用和可插入的组件。下面的示例描述了这样一个代理agent,它设置了可供实际程序使用的系统属性:
public class Agent { public static void premain(String arg) { System.setProperty("my-property", “foo”); } }
正如上面的代码所演示的,Java agent的定义与任何其他Java程序一样,只是 premain
将 main
方法替换为入口点。顾名思义,此方法在目标应用程序的主方法之前执行。除了适用于任何其他Java程序的标准规则之外,没有其他特定的编写agent代理的规则。作为最小的区别,Java agent接收单个可选参数,而不是零个或多个参数的数组。
要启动agent代理,必须将agent类和资源捆绑在一个jar中,并在jar manifest中将代理类属性设置为包含 premain
方法的代理类的名称(代理必须始终绑定为jar文件,不能以分解格式指定。)接下来,必须通过命令行上的 javaagent
参数引用jar文件的位置来启动应用程序:
java -javaagent:myAgent.jar -jar myProgram.jar
还可以将可选代理参数前置到此位置路径。以下命令启动Java程序,并将提供值 myOptions
作为参数的给定代理附加到 premain
方法:
java -javaagent:myAgent.jar=myOptions -jar myProgram.jar
通过重复 javaagent
命令可以附加多个代理。
然而,Java agent的功能远不止改变应用程序环境的状态;Java代理可以被授予访问Java instrumentation API的权限,从而允许代理修改目标应用程序的代码。Java虚拟机的这一鲜为人知的特性提供了一个强大的工具,可以促进面向方面编程的实现。
关于Java Instrumentation可以参考这篇文章: https://javakk.com/2237.html
通过向agent代理的 premain
方法添加第二个Instrumentation类型的参数,可以应用Java程序的这种修改。Instrumentation参数可用于执行一系列任务,从确定对象的确切大小(以字节为单位),到通过注册 ClassFileTransformers
来实际修改类实现。注册后,任何类装入器在装入类时都会调用 ClassFileTransformer
。调用时,类文件转换器有机会在加载所表示的类之前转换甚至完全替换任何类文件。通过这种方式,可以在类投入使用之前增强或修改类的行为,如下例所示:
public class Agent { public static void premain(String argument, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { @Override public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, // null if class was not previously loaded ProtectionDomain protectionDomain, byte[] classFileBuffer) { // return transformed class file. } }); } }
在将上述 ClassFileTransformer
注册到一个 Instrumentation
实例之后,每次加载一个类时都会调用该转换器。为此,转换器 transformer
接收类文件的二进制表示形式和对试图加载此类的类加载器的引用。
Java agent也可以在Java应用程序运行时注册。在这种情况下,instrumentation API允许重新定义已加载的类,这一特性称为“热更新”。不幸的是,重新定义加载的类仅限于替换方法体。在重新定义类时,不能添加或删除任何成员,也不能更改任何类型或签名。第一次加载类时,此限制不适用,并且在这些情况下, classBeingRedefined
参数设置为null。