什么是 Java Agent
是通过实现 Java 的 Instrumentation 接口, 实现对 Java 程序启动前或者启动后的一系列操作.
启动时拦截main的Agent
使用方式是在启动相应 jar 时加入启动参数 -javaagent: 使用的拦截jar
拦截jar所需满足的条件
- 在 META-INF 目录下的 MANIFEST.MF 文件中必须指定 Premain-Class 配置项
- premain-class 配置项指定的类必须提供了 premain() 方法
其中 permain 方法有两种重载1
2public static void premain(String agentArgs, Instrumentation inst); [1]
public static void premain(String agentArgs); [2]
假如两个方法同时存在指定的 premain-class 中,那么将会调用[1]的方法
具体项目可以查看这个示例: 项目地址
启动后进行增强
使用VirtualMachine的attach方法进行附着到指定 Pid 的 Java 虚拟机上, 并使用 loadAgent方法加载指定的jar
拦截jar所需满足的条件
- 在 META-INF 目录下的 MANIFEST.MF 文件中必须指定 Agent-Class 配置项
- Agent-Class 配置项指定的类必须提供了 agentmain() 方法
其中 agentmain 方法有两种重载1
2public static void agentmain(String agentArgs, Instrumentation inst); [1]
public static void agentmain(String agentArgs); [2]
同样的, 同时存在时, 仅会调用[1]方法
附着jar项目
进行附着的项目
能干啥
在使用了 Agent 后, 除了能在main方法或者在附着的时候执行某些代码之外, 还能通过Instrumentation对已有的 Class 进行修改
引入编译时的 Maven 插件
1 | <plugins> |
对已有类进行简单替换(使用premain进行举例)
首先创建一个想要被修改的类1
2
3
4
5
6public class PremainTarget {
public Integer getOne() {
return 1;
}
}
将编译后的类移入premain-demo项目的resource
再修改getOne的返回值为21
2
3public Integer getOne() {
return 2;
}
启动时再在启动参数中添加-javaagent参数
最后结果输出1
2
3use Premain agent
premain done
1
使用ByteBuddy进行修改
除了使用Class进行替换整个类文件,还可以借助其他工具进行简便的操作类,例如ByteBuddy.
主要用于协助字节码屏蔽/简化复杂Instrumentation API操作/提供类型安全的 API 和注解
让我们能够轻松进行字节码操作.
引入maven依赖
1 | <!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy --> |
在premain方法中指定Agent处理
下面简单的实现一个统计某个类的所有方法的耗时Agent1
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
30public static void premain(String agentArgs, Instrumentation inst) throws Exception{
// 对增强类中更细致的处理方式
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, module) -> {
// method 指定拦截的方法需要, ElementMatchers.any() 拦截所有方法
return builder.method(ElementMatchers.any())
// intercept() 指明方法的处理类
.intercept(MethodDelegation.to(TimeKeeperInterceptor.class));
};
// 使用 AgentBuild 定义处理的方式
new AgentBuilder
.Default()
// 根据包名前缀拦截类
.type(ElementMatchers.nameStartsWith("io.whileaway"))
// 拦截到的类由 transformer 处理
.transform(transformer)
// 安装到 Instrumentation
.installOn(inst);
}
public class TimeKeeperInterceptor {
public static Object intercept( Method method, Callable<?> callable)throws Exception {
long start = System.currentTimeMillis();
try {
return callable.call(); // 执行原函数
} finally {
System.out.println(method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");
}
}
}