javaagent介绍及使用
Javaagent是从JDK1.5开始引入的,agent的意思是代理,它实际上是个包含premain方法的jar包。要被监控的程序执行时需要在java启动参数中增加-javaagent参数,指定要加载的javaagent包。premain方法先于main方法执行,可以在类加载之前修改类定义,从而实现很多功能。
当Javaagent正确被加载,premain方法会自动被调用,并且会获取到Instrumentation对象的一个引用。通过Instrumentation这个对象,可以实现很多功能,比如修改类路径、获取已经加载的类、获取对象大小等等。其中最常用的是通过addTransformer方法添加类转化器,动态修改类定义,从而实现无侵入的应用监控。
从JDK1.6开始,提供了agentmain方法,提供了动态修改运行中的已经被加载的类的途径。一般通过VirtualMachine的attach(pid)方法获得VirtualMachine实例,随后调用loadagent方法将Javaagent的jar包加载到目标JVM中。
总结:Javaagent 只要作用在class被加载之前对其加载,插入我们需要添加的字节码。
实现Agent启动方法
启动时加载,实现permain
方法
1 2
| [1]public static void premain(String agentArgs, Instrumentation instrumentation); [2]public static void premain(String agentArgs);
|
运行时加载,实现agentmain
方法
1 2 3 4 5 6
| [1]public static void agentmain(String agentArgs, Instrumentation instrumentation); [2]public static void agentmain(String agentArgs);
VirtualMachine vm = VirtualMachine.attach("{pid}"); vm.loadAgent("{agent}", "{params}");
|
上面的两个方法只需要实现一个即可,且[1]的优先级是高于[2]的,即如果上面的两个方法同时出现,则只会执行[1]方法
使用示例
创建maven工程
Java Agent 是以 jar 包的形式存在。创建一个maven工程。引入插件自动生成MANIFEST.MF文件,替换成自己的项目中的AgentApp类路径:我的是xyz.xcyd.AgentApp
这样就能在打包的时候 将配置自动写入MANIFEST.MF文件
javaassist
包来修改字节码
jmockit
包VirtualMachine进行动态挂载Agent
完整 pom.xml 配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <?xml version="1.0" encoding="UTF-8"?> <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>javaagent</groupId> <artifactId>xyz.xcyd</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency> <dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit</artifactId> <version>1.33</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Premain-Class>xyz.xcyd.AgentApp</Premain-Class> <Agent-Class>xyz.xcyd.AgentApp</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>6</source> <target>6</target> </configuration> </plugin> </plugins> </build>
</project>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package xyz.xcyd;
import java.lang.instrument.Instrumentation;
public class AgentApp {
public static void premain(String agentArgs, Instrumentation inst){ System.out.println("Enter premain ......"); showExecTime(inst); }
public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("Enter agentmain ......"); showExecTime(inst); }
private static void showExecTime(Instrumentation inst){ inst.addTransformer(new MyTransformer(), true); }
}
|
实现自定义ClassFileTransformer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class MyTransformer implements ClassFileTransformer {
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.startsWith("xyz/xcyd")) { return classfileBuffer; } System.out.println("正在加载类:" + className);
try { ClassPool classPool = ClassPool.getDefault(); CtClass cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
for (CtMethod method : cl.getDeclaredMethods()) { method.addLocalVariable("start", CtClass.longType); method.insertBefore("start = System.currentTimeMillis();"); String methodName = method.getLongName(); String statement = String.format("System.out.println(\"方法:%s, 执行时间:\" + (System.currentTimeMillis() - start));", methodName); method.insertAfter(statement);
}
byte[] transformed = cl.toBytecode(); return transformed; } catch (Exception e) { e.printStackTrace(); }
return classfileBuffer; }
}
|
打包AgentApp,加入项目启动参数
将Agent项目打包成jar包
找一个自己的SpringBoot项目,在Idea的 Run/Debug Configurations
中快速配置 -javaagent:E:\IdeaWorkSpace\test-java-agent\target\xyz.xcyd-1.0-SNAPSHOT.jar
Agent Jar包的路径
![6tq7d0.md.png](https://s2.loli.net/2022/07/12/z6thIbknWBxrpJa.png)
启动项目 输出
1
| 方法:XXXXXXX$$EnhancerBySpringCGLIB$$ee06503b.login(java.lang.String), 执行时间:611
|