新建一个maven工程,命名为javaagent,其中pom.xml内容如下:
<?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>org.example</groupId>
<artifactId>javaagent</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<!--<main-Class>demo.Main</main-Class>-->
<Agent-Class>demo.Agent</Agent-Class>
<Premain-Class>demo.Agent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
</dependencies>
</project>
在src/main/java目录下新建一个package命名为demo,在demo包下创建两个java类,分别命名为Agent.java和MyTransformer.java。
Agent.java内容如下:
package demo;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new MyTransformer(agentArgs));
}
public static void premain(String agentArgs) {
}
}
MyTransformer.java内容如下:
package demo;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.*;
public class MyTransformer implements ClassFileTransformer {
private final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();
public MyTransformer() {
}
public MyTransformer(String agentArgs) {
System.out.println("Load successfully from args!");
add(agentArgs);
}
private void add(String methodString) {
String className = methodString.substring(0, methodString.lastIndexOf("."));
String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
List<String> list = methodMap.get(className);
if (list == null) {
list = new ArrayList<String>();
methodMap.put(className, list);
}
list.add(methodName);
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
if (methodMap.containsKey(className)) {
CtClass ctclass = null;
try {
ctclass = ClassPool.getDefault().get(className);
CtField startTime = new CtField(CtClass.intType, "startTime", ctclass);
ctclass.addField(startTime);
CtField endTime = new CtField(CtClass.intType, "endTime", ctclass);
ctclass.addField(endTime);
for (String methodName : methodMap.get(className)) {
CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);
ctmethod.insertBefore("startTime = System.currentTimeMillis();");
String endSrc = "endTime = System.currentTimeMillis();";
endSrc += "System.out.println(\"The method "+ctmethod.getName()+" cost time:\"+(endTime-startTime)+\"ms\");";
ctmethod.insertAfter(endSrc);
}
return ctclass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
使用mvn命令打包,执行命令:mvn assembly:assembly
打包成功后target目录下回出现javaagent-1.0-SNAPSHOT.jar和javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar,javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar包里包含依赖包里面的class。
这个时候javaagent的jar包已经打包好了,选择一个springboot项目,在启动时添加javaagent则可以统计方法执行耗时,启动命令:
java -javaagent:D:\javaagent\target\javaagent-1.0-SNAPSHOT-jar-with-dependencies.jar=com.example.demo.DemoController.demo -jar demo-0.0.1-SNAPSHOT.jar
其中com.example.demo.DemoController.demo是需要统计的方法,格式为类“全限定名.方法名”的形式,即这里监控的是类com.example.demo.DemoController里面的demo方法。
启动后访问demo方法,则会在控制台输出该方法的执行耗时:
Load successfully from args!
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.22.RELEASE)
2021-03-04 15:26:13.970 INFO 14772 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT on SZA191211513A with PID 14772 (D:\project\demo\target\demo-0.0.1-SNAPSHOT.jar started by z30006418 in D:\project\demo\target)
2021-03-04 15:26:13.974 INFO 14772 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2021-03-04 15:26:14.042 INFO 14772 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@77a567e1: startup date [Thu Mar 04 15:26:14 CST 2021]; root of context hierarchy
2021-03-04 15:26:15.371 INFO 14772 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
021-03-04 15:26:24.152 INFO 14772 --- [ttp-nio--exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2021-03-04 15:26:24.153 INFO 14772 --- [ttp-nio--exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2021-03-04 15:26:24.168 INFO 14772 --- [ttp-nio--exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
The method demo cost time:0ms