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
源码已上传GitHub:https://github.com/hxt970311/bytebuddy-agent-demo
3、使用AspectJ LTW监控方法执行耗时
AspectJ作为AOP编程的完全解决方案,提供了三种织入时机,分别为:
- compile-time:编译期织入,在编译的时候直接编译出包含织入代码的.class文件
- post-compile:编译后织入,增强已经编译出来的类
- load-time:在JVM进行类加载的时候进行织入
引入aspectj相关依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
编写Aspect:
@Aspect
public class MethodCostTimeAspect {
@Pointcut("execution(* com.ppdai..*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object logProfile(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
System.out.println(joinPoint.getSignature() + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
在src/main/resources
目录下添加META-INF/aop.xml
文件,指定Aspect类和需要被织入的类,内容如下:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<aspects>
<aspect name="com.ppdai.MethodCostTimeAspect"/>
<!--需要被织入的类-->
<weaver options="-verbose -showWeaveInfo">
<include within="com.ppdai..*"/>
</weaver>
</aspects>
</aspectj>
测试类:
public class AspectjLtwTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(new Random().nextInt(500));
}
}
运行测试类时指定agent aspectjweaver的jar包路径
-javaagent:/Users/hanxiantao/IdeaProjects/aspectj-ltw/target/aspectjweaver-1.8.13.jar
通过引入aspectjweaver依赖,到本地maven仓库获取aspectjweaver-1.8.13.jar
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
运行结果:
[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.8.13 built on Wednesday Nov 15, 2017 at 19:26:44 GMT
[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[AppClassLoader@18b4aac2] info using configuration /Users/hanxiantao/IdeaProjects/aspectj-ltw/target/classes/META-INF/aop.xml
[AppClassLoader@18b4aac2] info register aspect com.ppdai.MethodCostTimeAspect
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.AspectjLtwTest.main(java.lang.String[]))' in Type 'com.ppdai.AspectjLtwTest' (AspectjLtwTest.java:12) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)
[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.MethodCostTimeAspect.pointcut())' in Type 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java:17) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)
void com.ppdai.AspectjLtwTest.main(String[]) 方法耗时: 428ms
源码已上传GitHub:https://github.com/hxt970311/aspectj-ltw
参考:
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
https://www.javadoop.com/post/aspectj#Load-Time%20Weaving