1、什么是Java Agent
Java Agent提供了一种在加载字节码时,对字节码进行修改的方法。一共有两种方式执行:一种是在main方法执行之前,通过premain来实现;另一种是在程序运行中,通过attach api来实现
1)、Instrumentation
Instrumentation是JDK1.5提供的API,用于拦截类加载事件,并对字节码进行修改,它的主要方法如下:
public interface Instrumentation {
//注册一个转换器,类加载事件会被注册的转换器所拦截
void
addTransformer(ClassFileTransformer transformer, boolean canRetransform);
//重新触发类加载
void
retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
//直接替换类的定义
void
redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException, UnmodifiableClassException;
2)、premain()
方法
premain()
方法是在main()
方法之前运行的方法,运行时需要将agent程序打包成jar包,并在启动时添加命令来执行:
-javaagent:<jarpath>[=<options>]
premain()
共提供以下两种重载方法,JVM启动时会先尝试使用第一种方法,若没有会使用第二种方法
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
2、使用Byte Buddy监控方法执行耗时
Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类
pom.xml中引入Byte Buddy相关依赖,并指定MANIFEST.MF文件路径
<?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>com.ppdai</groupId>
<artifactId>bytebuddy-agent-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.8.20</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.8.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<!--指定MANIFEST.MF文件路径-->
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
MethodCostTime:
public class MethodCostTime {
@RuntimeType
public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
long start = System.currentTimeMillis();
try {
//原有函数执行
return callable.call();
} finally {
System.out.println(method + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
}
MyAgent:
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("MyAgent init...");
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder
//拦截任意方法
.method(ElementMatchers.any())
//委托
.intercept(MethodDelegation.to(MethodCostTime.class));
AgentBuilder.Listener listener = new AgentBuilder.Listener() {
@Override
public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) {
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
@Override
public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) {
}
@Override
public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
};
new AgentBuilder
.Default()
//指定需要拦截的类
.type(ElementMatchers.nameStartsWith("com.ppdai"))
.transform(transformer)
.with(listener)
.installOn(inst);
}
}
在src/main/resources
目录下添加META-INF/MANIFEST.MF
文件,内容如下:
Manifest-Version: 1.0
Premain-Class: com.ppdai.agent.MyAgent
Can-Redefine-Classes: true
使用mvn clean package
打包
测试类:
public class AgentTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(new Random().nextInt(500));
}
}
运行测试类时需要指定agent的jar包路径
-javaagent:<jarpath>[=<options>]
运行结果:
MyAgent init...
public static void com.ppdai.test.AgentTest.main(java.lang.String[]) throws java.lang.InterruptedException 方法耗时: 350ms
参考:
https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650130323&idx=3&sn=f22cf468dd7a85539c5ab40cee8bb9ef
https://bugstack.blog.csdn.net/article/details/100044939
https://blog.csdn.net/generalfu/article/details/106086475