前言
在主程序运行之前的agent模式有一些缺陷,例如需要在主程序运行前就指定javaagent参数,premain方法中代码出现异常会导致主程序启动失败等,为了解决这些问题,JDK1.6以后提供了在程序运行之后改变程序的能力。本文将重点介绍如何在程序运行中植入agent的方法。
案例
- 在AgentDemo工程中新增一个agentDemo2的类:
由于是在主程序运行后再执行,意味着我们可以获取主程序运行时的信息,这里我们打印出来主程序中加载的类名。
import java.lang.instrument.Instrumentation;
/**
* @Description
* @Author louxiujun
* @Date 2020/2/4 15:12
**/
public class AgentDemo2 {
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("load agent after main run. args=" + agentArgs);
Class<?>[] classes = inst.getAllLoadedClasses();
for (Class<?> cls : classes) {
System.out.println(cls.getName());
}
System.out.println("agent run completely");
}
}
- 添加Agent-Class参数,打成Jar包
pom文件build修改为如下内容,关键在于指定<Agent-Class>
标签值为AgentDemo2的完整路径,可以通过IDEA提供的copy reference复制过来。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!--方式1:在主程序运行之前的代理程序-->
<!--<Premain-Class>com.alibaba.ei.agent.AgentDemo</Premain-Class>-->
<!--方式2:在主程序运行之后的代理程序-->
<Agent-Class>com.alibaba.ei.agent.AgentDemo2</Agent-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
最后通过下面的指令,将新编写的agent包安装到本地maven仓库之中:
mvn clean install -Dmaven.test.skip=true
- 添加pom依赖,清空JVM启动参数
回到AgentTest工程,在pom文件中增加agent包依赖:
<dependency>
<groupId>com.alibaba.ei</groupId>
<artifactId>agentDemo</artifactId>
<version>${agentDemo.version}</version>
</dependency>
- 在主测试类AgentTest中增加如下的测试方法
在程序运行后加载,我们不可能在主程序中编写加载的代码,只能另写程序,那么另写程序如何与主程序进行通信?这里用到的机制就是attach机制,它可以将JVM A连接至JVM B,并发送指令给JVM B执行,JDK自带常用工具如jstack,jps等就是使用该机制来实现的。这里我们先用tomcat启动一个程序用作主程序B,再来写A程序代码。
我们使用VirtualMachine attach到目标进程,其中23764为tomcat进程的PID,可以使用jps命令获得,也可以使用VirtualMachine.list方法获取本机上所有的Java进程,再来判断Tomcat进程。很明显,这里的catalina进程就是我们在寻找的Tomcat进程,从而获取到本地Tomcat的进程号。
image.pngloadAgent方法第一个参数为Jar包在本机中的路径,第二个参数为传入agentmain的args参数,此处为null,运行程序。
private static void test3() {
try {
List<VirtualMachineDescriptor> virtualMachineDescriptorList = VirtualMachine.list();
String tomcatPID = "";
for (VirtualMachineDescriptor descriptor : virtualMachineDescriptorList) {
System.out.println(descriptor.id() + "," + descriptor.displayName());
if ((descriptor.displayName().startsWith("org.apache.catalina.startup.Bootstrap start"))) {
tomcatPID = descriptor.id();
break;
}
}
VirtualMachine vm = VirtualMachine.attach(tomcatPID);
vm.loadAgent("/Users/XXX/Documents/code/javaagent/agentDemo/target/agentDemo-1.0-SNAPSHOT.jar");
} catch (Exception e) {
e.printStackTrace();
}
}
然而什么都没有打印啊!是不是什么地方写错了呢?仔细想想就会发现,我们是将进程attach到了tomcat进程上,agent其实是在主程序B中运行的,所以程序A中自然就不会进行打印,我们跳回tomcat程序的控制台,查看结果。
image.png可以看到,agentmain方法中的代码已经在主程序中顺利运行了,并且打印出了程序中加载的类!
网友评论