Java Agent开发框架
Java Agent开发框架,简称JADE,是一个用Java语言实现的用于开发智能Agent的软件框架。JADE系统支持多个agent之间的协调,并提供了一个标准的通信语言FIPA- acl的实现,方便了agent之间的通信,并允许系统的服务检测。JADE最初由意大利电信开发,并作为免费软件分发。
在jdk中Javaagent是java命令的一个参数。
参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:
- 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
- Premain-Class 指定的那个类必须实现 premain() 方法,如下:JVM 会优先加载 带
Instrumentation
签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。这个逻辑在sun.instrument.InstrumentationImpl 类中:
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
在命令行使用
#指定agent程序并运行该类
java -javaagent:javaagent.jar ./HelloWorld.jar
其中javaagent.jar承载 javaagent类,HelloWorld.jar含有javaagent需要去代理的类。
一个java程序中-javaagent
参数的个数是没有限制的,所以可以添加任意多个javaagent。所有的java agent会按照你定义的顺序执行,例
java -javaagent:agent1.jar -javaagent:agent2.jar -jar MyProgram.jar
程序执行的顺序将会是:
MyAgent1.premain -> MyAgent2.premain -> MyProgram.main
javaagent jar包的构建及使用
- 定义一个 MANIFEST.MF 文件,必须包含 Premain-Class 选项,通常也会加入Can-Redefine-Classes 和 Can-Retransform-Classes 选项。
- 创建一个Premain-Class 指定的类,类中包含 premain 方法,方法逻辑由用户自己确定。
- 将 premain 的类和 MANIFEST.MF 文件打成 jar 包。
- 使用参数 -javaagent: jar包路径 启动要代理的方法。
其中第一点可以手动配置,也可以借助maven配置
1.手动
在 resources
目录下新建目录:META-INF
,在该目录下新建文件MANIFEST.MF
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.bingbaihanji.agent.AgentMain
注意第5行有空行。
2.maven
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!--指定实现premain()方法的类-->
<Premain-Class>com.bingbaihanji.agent.AgentMain</Premain-Class>
<!--指定实现agentmain()方法的类-->
<Agent-Class>com.bingbaihanji.agent.AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
MANIFEST.MF文件参数说明
Premain-Class :包含 premain 方法的类(类的全路径名)
Agent-Class :包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)
实例
1.目录结构
├─.idea
│ ├─artifacts
│ └─codeStyles
└─src
├─main
│ ├─java
│ │ └─com
│ │ └─bingbaihanji
│ │ └─agent
│ └─resources
└─test
└─java
└─com
└─bingbaihanji
2.pom.xml
其中javassist为自己构建,主要为了支持文本块字符串
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bingbaihanji</groupId>
<artifactId>javaagent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.bingbaihanji.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!--指定实现premain()方法的类-->
<Premain-Class>com.bingbaihanji.agent.AgentMain</Premain-Class>
<!--指定实现agentmain()方法的类-->
<Agent-Class>com.bingbaihanji.agent.AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!--打包时,包含所有依赖的jar包 (配置完执行 mvn package即可)-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<!--给jar包起的别名-->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<!--指定实现premain()方法的类-->
<Premain-Class>com.bingbaihanji.agent.AgentMain</Premain-Class>
<!--指定实现agentmain()方法的类-->
<Agent-Class>com.bingbaihanji.agent.AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
agent类
package com.bingbaihanji.agent;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class AgentMain {
/**
* jvm 参数形式启动,运行此方法
*
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("premain");
final String config = agentArgs;
ClassPool pool = ClassPool.getDefault();
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException {
// System.out.println("正在加载的类:" + className.replace("/", "."));
// printStackMessage();
if (!className.replaceAll("/", ".").startsWith(config)) {
return null;
}
try {
ClassClassPath classPath = new ClassClassPath(this.getClass());
pool.insertClassPath(classPath);
className = className.replaceAll("/", ".");
// 获取类
CtClass ctClass = pool.get(className);
System.out.println("正在加载的类:" + className.replace("/", "."));
// printStackMessage();
// 获取所有方法
for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
newMethod(ctMethod);
}
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}
// 方法拷贝以及注入
public static void newMethod(CtMethod oldCtMethod) throws CannotCompileException, NotFoundException {
CtMethod copy = CtNewMethod.copy(oldCtMethod, oldCtMethod.getDeclaringClass(), null);
copy.setName(oldCtMethod.getName() + "$agent");
oldCtMethod.getDeclaringClass().addMethod(copy);
if (oldCtMethod.getReturnType().equals(CtClass.voidType)) {
oldCtMethod.setBody(String.format(voidSources, oldCtMethod.getName(), oldCtMethod.getName()));
} else {
oldCtMethod.setBody(String.format(source, oldCtMethod.getName(), oldCtMethod.getName()));
}
}
// $$ 表示两个参数 $w 表示强转
public static String source = """
{
long begin = System.currentTimeMillis();
Object result;
try {
result = ($w)%s$agent($$);
} finally {
long end = System.currentTimeMillis();
System.out.println("%s方法运行时间为" + (end - begin) + "ms");
}
return ($r) result;
}
""";
public static String voidSources = """
{
long begin = System.currentTimeMillis();
try {
%s$agent($$);
} finally {
long end = System.currentTimeMillis();
System.out.println("%s方法运行时间为" + (end - begin) + "ms");
}
}
""";
// 打印堆栈调用信息
public static void printStackMessage() {
System.out.println("堆栈调用信息");
for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
System.out.println(element);
}
}
// /**
// * 动态 attach 方式启动,运行此方法
// *
// * @param agentArgs
// * @param inst
// */
// public static void agentmain(String agentArgs, Instrumentation inst) {
// System.out.println("agentmain");
// customLogic(inst);
// }
// 2012 2020
}
测试类
package com.bingbaihanji;
public class Hello {
public static void main(String[] args) {
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
jvm参数
-javaagent:D:/javaProject/javaagent/target/javaagent-1.0-SNAPSHOT.jar=com.bingbaihanji
运行结果
premain
正在加载的类:com.bingbaihanji.Hello
main方法运行时间为5007ms