编写Java Agent插件

编写Java Agent插件

我们通过使用Instrumentation编写一个Java Agent程序,实现业务代码插桩,在方法执行之前插入埋点,以及在方法执行异常或者执行完成之前插入埋点,用于收集方法执行日记信息。Java Agent本质上是一个插件。

新建一个项目,项目构建工具使用maven,项目名为my-java-agent。创建一个类并编写premain方法,如代码清单7-1所示。

代码清单7-1 MyJavaAgent的premain方法

public class MyJavaAgent {
    public static void premain(String agentOps, Instrumentation instrumentation) {
        System.out.println("premain function run...");
    }
}

修改maven依赖配置文件,配置项目打包所需的插件,代码如下:

<build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <!-- 把依赖的jar包一起打包 -->
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <!-- 注册premain的class -->
                        <manifestEntries>
                            <Premain-Class>com.wujiuye.agent.MyJavaAgent</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

其中Premain-Class标签用于配置MANIFEST.MF文件的Premain-Class参数。打包后将jar包解压,可在META-INF目录下看到MANIFEST.MF文件,文件内容如下:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: wjy
Build-Jdk: 1.8.0_144
Premain-Class: com.wujiuye.agent.MyJavaAgent

另外新建一个demo项目,在main方法中调用业务类的方法。如代码清单7-2所示。

代码清单7-2 demo项目代码

public class UserService {

    public Map<String, Object> queryUser(String username, Integer age) {
        Map<String, Object> result = new HashMap<>();
        result.put("username", username);
        result.put("age", age);
        return result;
    }

}

public class DemoAppcliction {

    public static void main(String[] args) {
        System.out.println("main function runing...");
        Map<String, Object>  user = new UserService()
                .queryUser("wujiuye", 25);
        System.out.println(user);
    }

}

如果想要将my-java-agent插件附着在demo项目运行,可在使用java命令启动demo项目时配置-javaagent参数,或者在IDEA中配置VM options加上-javaagent参数。以在IDEA中配置VM options为例,如图7.1所示:

图7.1 demo项目启动VM参数配置

demo项目运行输出如下:

premain function run...
main function runing...
{age=25, username=wujiuye}

可见,我们所编写的my-java-agent程序的premain方法先于demo项目的main方法执行了。

premain方法的两个参数:

◆agentOps:用于接收我们在配置javaagent参数时附加的信息,例如:

-javaagent:my-java-agent-1.0-jar-with-dependencies.jar=com.wujiuye

则premain方法中接收到agentOps参数的值为“com.wujiuye”。

◆instrumentation:Instrumentation实例,由JVM传递。