Java Agent是一种Java应用程序的附加组件,它可以通过Java虚拟机的自定义类加载器来加载并执行,从而在应用程序生命周期内提供额外的功能和服务。Java Agent常见的应用场景包括:性能监测、应用程序调试、代码覆盖率和行为分析、安全检测、依赖项注入等。本文将介绍Java Agent的完整使用攻略,并给出两个实际示例说明。
一、Java Agent的使用和开发环境
1.1 使用环境
Java Agent 一般用于在 jvm 启动的时候动态地修改 jvm 的行为,而不需要修改源码。所以 Java Agent 主要使用在以下环境:
- Web 应用程序服务器
- 单元测试框架中
- 独立的 java 应用程序
1.2 开发环境
Java Agent的开发环境主要需要以下两个组件:
- JDK:安装了jdk的开发环境
- Maven:Java 项目管理工具
二、Java Agent的使用攻略
2.1 使用方式
使用Java Agent,Java程序至少需要在启动时添加以下JVM参数:
java -javaagent:/path/to/agent.jar -jar /path/to/application.jar
其中/path/to/agent.jar
是Java Agent的JAR包路径,/path/to/application.jar
是应用程序的JAR包路径。
在启动时添加的JVM参数-javaagent
指定了Java Agent的JAR包的位置,它将被JVM的Instrumentation API加载并启动。Java Agent的JAR包中需要提供一个公共静态方法premain
或agentmain
来进行Agent启动和初始化。
2.2 代码示例
示例1:追踪方法调用链,输出相关信息
以下示例使用java.lang.instrument.Instrumentation
接口提供的API,追踪方法调用链,并输出相关信息。
- 编写Java Agent的代码
在agent.jar中创建一个类TracingAgent,实现java.lang.instrument.ClassFileTransformer
接口,实现单个方法transform
,代码如下:
package com.example;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.logging.Logger;
/**
* 追踪方法调用链,并输出相关信息
*/
public class TracingAgent implements ClassFileTransformer {
private static Logger logger = Logger.getLogger(TracingAgent.class.getName());
/**
* 该方法会在类加载时触发,执行字节码转换
*
* @param loader 目标类的加载器,一般为系统类加载器
* @param className 目标类名称
* @param classBeingRedefined 被重定义的类
* @param protectionDomain 受保护的域
* @param classfileBuffer 目标类的字节码
* @return 转换后的字节码
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
logger.info("Transforming class: " + className);
// 可以在这里插入自定义的字节码转换操作
return classfileBuffer;
}
}
- 在premain方法中注册TracingAgent
package com.example;
import java.lang.instrument.Instrumentation;
public class AgentMain {
/**
* Java Agent的入口方法,用于完成Agent的初始化
*
* @param agentArgs 代理参数
* @param instrumentation Instrumentation 实例,提供字节码转换、动态类定义等 API
*/
public static void premain(String agentArgs, Instrumentation instrumentation) {
// 在premain方法中进行Agent的初始化操作
System.out.println("AgentMain.premain called");
// 注册TracingAgent
TracingAgent agent = new TracingAgent();
instrumentation.addTransformer(agent);
}
}
- 在构建脚本中加入agent.jar的打包
在Maven的pom.xml文件中加入以下dependencies和plugins,用于依赖AspectJ和打包agent.jar:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<!-- Maven plugin 和 AspectJ weaver 共用的配置项,指定要编织的目标类 -->
<weaveDirectories>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
</weaveDirectories>
<aspectLibraries>
<aspectLibrary>
<groupId>com.example</groupId>
<artifactId>agent</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<!-- phase 设置为 pre-integration-test,确保在测试之前运行 Agent -->
<phase>pre-integration-test</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 打包agent.jar并启动应用程序
在控制台上使用以下命令打包agent.jar
mvn package
然后运行Java应用程序,并在启动时添加以下JVM参数:
java -javaagent:/path/to/agent.jar -cp /path/to/application.jar com.example.App
其中com.example.App
为应用程序的入口类。
示例2:给Bean对象添加自定义注解
以下示例通过Java Agent为Bean对象添加自定义注解。
- 编写Java Agent的代码
在agent.jar中创建一个类AnnotationAgent,实现java.lang.instrument.ClassFileTransformer
接口,实现单个方法transform
,代码如下:
package com.example;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
/**
* 该 Agent 会在类文件加载时进行处理,为其中含有特定注解的类添加新的注解
*/
public class AnnotationAgent implements ClassFileTransformer {
private static final String ANNOTATION = "com.example.AddAnnotation";
/**
* 该方法会在类加载时触发,执行字节码转换
*
* @param loader 目标类的加载器,一般为系统类加载器
* @param className 目标类名称
* @param classBeingRedefined 被重定义的类
* @param protectionDomain 受保护的域
* @param classfileBuffer 目标类的字节码
* @return 转换后的字节码
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
byte[] byteCode = classfileBuffer;
ClassPool classPool = ClassPool.getDefault();
// 从目标类加载器加载目标类
classPool.insertClassPath(new LoaderClassPath(loader));
CtClass ctClass = null;
try {
ctClass = classPool.get(className.replace("/", "."));
// 扫描类中的方法,为含有特定注解的方法添加新的注解
CtMethod[] methods = ctClass.getDeclaredMethods();
for (CtMethod method : methods) {
if (method.getAnnotation(ANNOTATION) != null) {
addAnnotation(method);
System.out.println("AddAnnotation added to method: " + method.getName());
}
}
byteCode = ctClass.toBytecode();
} catch (NotFoundException | CannotCompileException | NullPointerException e) {
e.printStackTrace();
} finally {
if (ctClass != null) {
ctClass.detach();
}
}
return byteCode;
}
/**
* 给方法添加自定义注解
*
* @param method 要添加注解的方法
* @throws CannotCompileException
*/
private void addAnnotation(CtMethod method) throws CannotCompileException {
CtClass addAnnotation = null;
try {
addAnnotation = method.getDeclaringClass().getClassPool().get(ANNOTATION);
ConstPool constpool = method.getMethodInfo().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
javassist.bytecode.annotation.Annotation jannot = new javassist.bytecode.annotation.Annotation(ANNOTATION, constpool);
attr.addAnnotation(jannot);
method.getMethodInfo().addAttribute(attr);
} catch (NotFoundException e) {
e.printStackTrace();
} finally {
if (addAnnotation != null) {
addAnnotation.detach();
}
}
}
}
- 在premain方法中注册AnnotationAgent
package com.example;
import java.lang.instrument.Instrumentation;
public class AgentMain {
/**
* Java Agent的入口方法,用于完成Agent的初始化
*
* @param agentArgs 代理参数
* @param instrumentation Instrumentation 实例,提供字节码转换、动态类定义等 API
*/
public static void premain(String agentArgs, Instrumentation instrumentation) {
// 在premain方法中进行Agent的初始化操作
System.out.println("AgentMain.premain called");
// 注册AnnotationAgent
AnnotationAgent agent = new AnnotationAgent();
instrumentation.addTransformer(agent);
}
}
- 定义自定义注解
在agent.jar中定义一个自定义注解 AddAnnotation 如下:
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 要添加到带有注释的方法的注释
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AddAnnotation {
}
- 在构建脚本中加入agent.jar的打包
在Maven的pom.xml文件中加入以下dependencies和plugins,用于依赖AspectJ和打包agent.jar:
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.26.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<!-- Maven plugin 和 AspectJ weaver 共用的配置项,指定要编织的目标类 -->
<weaveDirectories>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
</weaveDirectories>
<aspectLibraries>
<aspectLibrary>
<groupId>com.example</groupId>
<artifactId>agent</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<!-- phase 设置为 pre-integration-test,确保在测试之前运行 Agent -->
<phase>pre-integration-test</phase>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 打包agent.jar并启动应用程序
在控制台上使用以下命令打包agent.jar
mvn package
然后在应用程序的 jar 文件中随便添加一个带有 @AddAnnotation
注解的方法,例如:
package com.example;
public class Foo {
// 添加注解,用来测试 AnnotationAgent 的效果
@AddAnnotation
public void doSomething() {
// do something
}
}
最后运行Java应用程序,并在启动时添加以下JVM参数:
java -javaagent:/path/to/agent.jar -cp /path/to/application.jar com.example.App
其中com.example.App
为应用程序的入口类。
三、总结
本文介绍了Java Agent的完整使用攻略,包括Java Agent的使用和开发环境、使用方式、Java Agent实例、打包和启动方式等。Java Agent是一个非常有用的技术,可以大大拓展应用程序的功能和服务,并为开发人员提供更多的调试和分析工具。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:什么是Java Agent? - Python技术站