java-agent探针技术
java-agent在无侵入监控上很常见,skywalking和Arthas都有用到这项技术。
两类主要的入口:premain与agentmain。前者使用-javaagent指定后执行,后者使用VM api loadAgent后执行。
premian和agentmain都有两种重载:
public static void premain(String agentArgs)
public static void premain(String agentArgs, Instrumentation instrumentation)
public static void agentmain(String agentArgs)
public static void agentmain(String agentArgs, Instrumentation instrumentation)
其中:agentArgs 是跟随 -javaagent:xxx.jar=yyy 传入的 yyy 字符串,而instrumentation就是我们的agent增强应用的关键。本文不会详细介绍instrumentation。Instrumentation javadoc。多参方法优先单参方法,同时存在时单参不会生效。
定义HookMain和ClassFileTransformer,前者提供premain和agentmain方法入口,后者提供一个class文件字节码转换器,在premain方法通过instrumentation将该转换器添加进去,ClassFileTransformer的transform可以将byte[]转换为byte[],结合字节码操作库(ASM、Javassist、ByteBuddy)可以方便的对类就行加载时增强。
/**
* 入口
*/
public class HookMain {
/**
* -javaagent:xxx方式 增强
*
* @param agentArgs agentArgs 是跟随 -javaagent:xxx.jar=yyy 传入的 yyy 字符串
* @param instrumentation 主要是调用这个对象的方法增强
*/
public static void premain(String agentArgs, Instrumentation instrumentation){
System.out.println("premain ----------------"+agentArgs);
instrumentation.addTransformer(new ClassExceptionTransformer());
}
/**
* @param agentArgs agentArgs 是跟随 javaagent:xx.jar=yyy 传入的 yyy 字符串
* @param instrumentation
*/
public static void agentmain(String agentArgs, Instrumentation instrumentation){
System.out.println("agentmain ----------------"+agentArgs);
}
}
/**
* 输出所有的class name
* @author lige
* @date 2021-9-18
*/
public class ClassExceptionTransformer implements ClassFileTransformer {
/**
* 这一步需要借助ASM、Javassist、ByteBuddy这样的类库方便操作
*/
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("loading " + className);
return classfileBuffer;
}
}
上面两个类将HookMain的premain方法会将ClassFileTransformer添加到instrumentation,同时输出信息,ClassExceptionTransformer会输出所有(其实也不是所有,有些基本类比如lang下面的时先于premain加载的:基本环境 > premain > main > agentmain)加载的类名称到控制台。
打包可以借助一个maven插件自动添加META-INF/MANIFEST.MF
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.tomatogames.agent.HookMain</Premain-Class>
<Agent-Class>com.tomatogames.agent.HookMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
效果和你自己写是一样的:
Manifest-Version: 1.0
Premain-Class: com.demo.agent.HookMain
Built-By: lige
Agent-Class: com.demo.agent.HookMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_291
得到agent.jar之后你有两种方式使用它:(假设你要监控的目标jar是example.jar,应用名称是com.example.demo.DemoApplication)
1.启动时设置-javaagent参数
java -javaagent:pathto/agent.jar -jar example.jar
2.通过attach手段动态使用
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { //列出所有的虚拟机 List<VirtualMachineDescriptor> list = VirtualMachine.list(); //找到应用名为com.example.demo.DemoApplication的 VirtualMachineDescriptor descriptor = list.stream().filter(i -> "com.example.demo.DemoApplication".equals(i.displayName())).findAny().orElse(null); //通过id attach该vm VirtualMachine virtualMachine= VirtualMachine.attach(descriptor.id()); //加载loadAgent并设置参数aaaa virtualMachine.loadAgent("pathto/agent-1.0.jar","aaaa"); }
注:com.sun.tools.attach.VirtualMachine是 jdk1.8.0_291\lib\tools.jar里面的类如果,找不到你可以将该jar添加到你的项目classpath。
强啊