JavaAgent的原理来源这里就不多说了,有兴趣的同学请参考一下两篇文章:
这篇文章主要时为了讲述JavaAgent在使用中的一些细节。
对JavaAgent有所了解的人都知道,JavaAgent有两种启动方式permain和agentmain分别是启动时触发和启动后嵌入,以下简称onload和attach。
探针JAR
JavaAgent程序的核心代码需要一个jar包,这个jar中必须存在一个/META-INF/MANIFEST.MF文件,文件内容如下:
Manifest-Version: 1.0
Agent-Class: com.yz.javaagent.agent.JavaAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
注意:最后一行必须是空的
Agent-Class就是你的核心类了,代码如下:
/**
* attach 方式启动
* @param args
* @param inst
* @throws Exception
*/
public static void agentmain(String args, Instrumentation inst) throws Exception {
System.out.println("args:"+args);
String[] split = args.split(",");
String coreJarPath = split[0];
String className = split[1];
String methodName = split[2];
inst.appendToBootstrapClassLoaderSearch(new JarFile(coreJarPath));
ClassLoader classLoader = JavaAgent.class.getClassLoader();
Class<?> aClass1 = classLoader.loadClass("com.yz.javaagent.core.transformer.ClassUtil");
byte[] fileByte = (byte[]) aClass1.getMethod("getClassFileByte", String.class,String.class).invoke(null, className,methodName);
ClassDefinition classDefinition = new ClassDefinition(classLoader.loadClass(className),fileByte);
//attach 方式时使用
inst.redefineClasses(classDefinition);
}
/**
* onload 启动前运行
* @param args
* @param instrumentation
*/
public static void premain(String args,Instrumentation instrumentation) {
//onload时使用
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
return new byte[0];
}
});
}
这里是参考了Arthas的源码,所以使用了classloader分离的方式,这样的好处是:不需要在目标应用中引用ASM.jar(这里用的是javassist)。
attach方式启动时进程就会触发agentmain方法,通过instrumentation.appendToBootstrapClassLoaderSearch方法将core.jar包嵌入到目标应用的classloader中,因为下边会通过反射调用里边的ClassUtil类
git:https://gitee.com/lovelyesz/javaagent.git
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
注意:声明package时将依赖的jar也打进去,依赖的依赖jar是打不进去的,只打入一层
这时com.yz.javaagent.core.transformer.ClassUtil这个类已经放入到了目标应用JVM的方法区了,使用反射调用getClassFileByte获得目标类的字节数组,这个自然是我们使用ASM技术修改后的了,instrumentation#redefineClasses刷新class代码。
调用Agent.jar
-
attach方式
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent(agentJarPath,coreJatPath+","+args);
virtualMachine.detach();
pid:目标应用的进程ID
agentJarPath:探针jar的路径
coreJarPath:核心jar的路径
-
onload方式
java -jar demo.jar -agentlib:{agentJarPath}=k1=v1,k2=v2,k3=v3