一、Java Agent 简介
在JDK1.5以后,我们可以使用agent技术构建一个独立于应用程序的代理程序(即为Agent),用来协助监测、运行甚至替换其他JVM上的程序。使用它可以实现虚拟机级别的AOP功能。
Agent分为两种,一种是在主程序之前运行的Agent,一种是在主程序之后运行的Agent(前者的升级版,1.6以后提供),下面这两种我都举个例子。
二、在主程序运行之前的代理
1、构建一个代理程序
代码例子很简单,这里是用maven写的例子,如下:
public class AgentTest {
/**
* 在主程序运行之前执行
* @param agentArgs
* @param isnt
*/
public static void premain(String agentArgs,Instrumentation inst) {
System.out.println("premain start");
System.out.println(agentArgs);
}
}
在构建的时候pom中加入如下内容
<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.suibibk</groupId>
<artifactId>AgentTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>
com.suibibk.AgentTest
</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
主要是
<Premain-Class>
com.suibibk.AgentTest
</Premain-Class>
maven install构建获得jar包路径
D:\Work\eclipse_workspace\AgentTest\target\AgentTest-0.0.1-SNAPSHOT.jar
2、构建一个被代理的程序
public class Test2 {
public static void main(String[] args) {
System.out.println("main2");
}
}
运行时添加如下VM Arguments参数(eclipse右键Run Configurations):
-javaagent:D:\Work\eclipse_workspace\AgentTest\target\AgentTest-0.0.1-SNAPSHOT.jar=haha
运行,可以发现日志为:
premain start
haha
main2
在运行之前先执行了代理程序
三、在主程序运行之后的代理
在主程序运行之前的agent模式有一些缺陷,例如需要在主程序运行前就指定javaagent参数,premain方法中代码出现异常会导致主程序启动失败等,为了解决这些问题,JDK1.6以后提供了在程序运行之后改变程序的能力。它的实现步骤和之前的模式类似。
1、构建一个代理程序
public class AgentTest {
/**
* 在主程序运行之后执行
* @param args
* @param inst
*/
public static void agentmain(String args,Instrumentation inst) {
System.out.println("loadagent after main run.args="+args);
Class<?>[] classes = inst.getAllLoadedClasses();
for (Class<?> cls : classes) {
System.out.println(cls.getName());
}
System.out.println("agent run completely");
}
}
我们复用上面的类,将premain方法修改为agentmain方法,由于是在主程序运行后再执行,意味着我们可以获取主程序运行时的信息,这里我们打印出来主程序中加载的类名。
pom.xml改动一下,把
<Premain-Class>
com.suibibk.AgentTest
</Premain-Class>
改为
<Agent-Class>
com.suibibk.AgentTest
</Agent-Class>
然后构建,获得路径。
D:\Work\eclipse_workspace\AgentTest\target\AgentTest-0.0.1-SNAPSHOT.jar
2、构建一个被代理程序
我这里的代理程序是一个常驻内存的web程序,也就是我的个人网站suibibk.com,当然只是本地启动,然后用cmd执行jsp -lv获取PID为:17276
3、构建一个代理与被代理程序通信的程序
在程序运行后加载,我们不可能在主程序中编写加载的代码,只能另写程序,那么另写程序如何与主程序进行通信?这里用到的机制就是attach机制,它可以将JVM A连接至JVM B,并发送指令给JVMB执行,JDK自带常用工具如jstack,jps等就是使用该机制来实现的。
public class Test {
public static void main(String[] args) {
try {
VirtualMachine vm = VirtualMachine.attach("17276");
vm.loadAgent("D:\\Work\\eclipse_workspace\\AgentTest\\target\\AgentTest-0.0.1-SNAPSHOT.jar");
} catch (Exception e) {
e.printStackTrace();
}
}
}
我这里刚开始会一直报错,搞不懂为什么找不到VirtualMachine类,然后一怒之下我直接去添加jdk lib目录下的tools.jar,然后就可以了。
执行该程序,发现被代理程序打印了日志:
ch.qos.logback.core.spi.DeferredProcessingAware
ch.qos.logback.classic.Logger
ch.qos.logback.core.spi.AppenderAttachable
org.slf4j.spi.LocationAwareLogger
ch.qos.logback.classic.spi.LoggerContextVO
...
...
java.lang.Object
agent run completely
可以看到,agentmain方法中的代码已经在主程序中顺利运行了,并且打印出了程序中加载的类!