Java Agent开发框架

Java Agent开发框架,简称JADE,是一个用Java语言实现的用于开发智能Agent的软件框架。JADE系统支持多个agent之间的协调,并提供了一个标准的通信语言FIPA- acl的实现,方便了agent之间的通信,并允许系统的服务检测。JADE最初由意大利电信开发,并作为免费软件分发。

在jdk中Javaagent是java命令的一个参数。

参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求:

  1. 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。
  2. 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包的构建及使用
  1. 定义一个 MANIFEST.MF 文件,必须包含 Premain-Class 选项,通常也会加入Can-Redefine-Classes 和 Can-Retransform-Classes 选项。
  2. 创建一个Premain-Class 指定的类,类中包含 premain 方法,方法逻辑由用户自己确定。
  3. 将 premain 的类和 MANIFEST.MF 文件打成 jar 包。
  4. 使用参数 -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

人生不作安期生,醉入东海骑长鲸