Java Agent 是一种强大的工具,它允许我们在 Java 程序运行时修改字节码并注入自定义逻辑。结合 ASM(Java 字节码操作库),我们可以编写一个 Java Agent,用于监控方法的执行时间并打印方法参数。这种技术对于性能分析和调试非常有用。
ASM(原名"ANALYZER, SCANNER and MODEL")是一个流行的 Java 字节码操作库,它提供了强大的功能来读取、修改和生成字节码。ASM 可以直接操作字节码,而无需依赖于源代码或反编译。
ASM字节码插桩探索这篇文章也可以先了解一下。
以下是对 ASM 的介绍:
功能丰富:ASM 提供了广泛的功能和 API,可以对字节码进行细粒度的操作。它支持读取和分析现有的字节码,修改字节码并生成新的字节码。ASM 还提供了访问器(Visitor)模式,使得开发人员可以在扫描字节码时添加自定义的处理逻辑。
快速高效:ASM 被设计为快速和高效的字节码操作库。它使用了一些优化技术来提高性能,并且具有较低的内存占用。由于它直接操作字节码,相对于其他基于源代码的操作库,ASM 在性能上具有一定的优势。
广泛应用:ASM 在 Java 生态系统中得到广泛的应用。它被用于各种场景,包括字节码增强、动态代理、AOP(面向切面编程)、代码生成、静态分析、代码优化等。许多框架和工具,如 Spring、Hibernate、JUnit 等,都使用了 ASM 来实现字节码级的功能。
Java Agent 是一个独立的 Java 程序,可以在应用程序启动时动态地修改字节码。它利用了 Java 虚拟机的 Instrumentation API,通过字节码转换技术来修改和增强正在运行的程序。
以下是对 Java Agent 的介绍:
动态修改字节码:Java Agent 允许开发人员在应用程序运行时动态地修改字节码。它可以在类加载过程中拦截和转换字节码,并注入自定义的逻辑。这使得开发人员可以在运行时对应用程序的行为进行增强和定制。
监控和诊断:Java Agent 可以用于监控和诊断应用程序的性能和行为。通过在字节码中插入监控代码,可以测量方法的执行时间、内存使用情况等。这对于性能分析、热点定位和优化非常有用。
AOP 和代码生成:Java Agent 可以实现 AOP(面向切面编程)的功能,通过在字节码中插入切面逻辑来实现横切关注点。它还可以用于动态生成代码,例如动态代理、动态子类等。
类加载器级别的增强:Java Agent 在类加载器级别操作字节码,因此可以对整个应用程序进行增强,包括第三方库和框架。这使得开发人员能够在不修改源代码的情况下对应用程序进行定制。
应用场景广泛:Java Agent 在许多应用场景中得到广泛应用。它被用于应用程序监控、性能分析、日志记录、安全检查、事务管理等领域。许多知名的开源项目和商业工具,如 JProfiler、New Relic、Byteman 等,都是基于 Java Agent 技术实现的。
综上所述,ASM 提供了强大的字节码操作功能,而 Java Agent 利用了 ASM 的能力,使开发人员能够在应用程序运行时动态地修改字节码,从而实现各种定制化和增强功能。接下来就开始干活。
新建一个java项目,pom文件引入相应jar
<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.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>SQLQueryAgentartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-bomartifactId>
<version>9.5version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asmartifactId>
dependency>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-commonsartifactId>
dependency>
<dependency>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-treeartifactId>
dependency>
<dependency>
<groupId>org.javassistgroupId>
<artifactId>javassistartifactId>
<version>3.28.0-GAversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<configuration>
<createDependencyReducedPom>falsecreateDependencyReducedPom>
<finalName>${project.artifactId}finalName>
<filters>
<filter>
<artifact>*:*artifact>
<excludes>
<exclude>META-INF/**exclude>
excludes>
filter>
filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>com.*.*.StartUpPremain-Class>
<Can-Redefine-Classes>trueCan-Redefine-Classes>
<Can-Retransform-Classes>trueCan-Retransform-Classes>
manifestEntries>
transformer>
transformers>
configuration>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
创建一个agent启动类,pom文件中需要修改Premain-Class标签为你创建的agent启动类
public class StartUp {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("-----------------启动开始---------------------");
inst.addTransformer(new Transformer(),true);
System.out.println("-----------------启动结束---------------------");
}
}
我的项目使用的是hibernate,所以我找到了SessionImpl类的listCustomQuery方法,我在这个方法执行的时候进行参数打印,打印出sql和params。
源码如下:
@Override
public List listCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) {
checkOpenOrWaitingForAutoClose();
// checkTransactionSynchStatus();
if ( log.isTraceEnabled() ) {
log.tracev( "SQL query: {0}", customQuery.getSQL() );
}
CustomLoader loader = getFactory().getQueryPlanCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() );
autoFlushIfRequired( loader.getQuerySpaces() );
dontFlushFromFind++;
boolean success = false;
try {
List results = loader.list( this, queryParameters );
success = true;
return results;
}
finally {
dontFlushFromFind--;
delayedAfterCompletion();
afterOperation( success );
}
}
新建Transformer 类:
public class Transformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer){
if (className != null && className.equals("org/hibernate/internal/SessionImpl")) {
System.out.println("Transforming class: " + className);
try {
ClassReader classReader = new ClassReader(className);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor classVisitor = new SkyWorthClassVisitor(classWriter);
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();
}catch (Exception e){
e.printStackTrace();
}
}
return classfileBuffer;
}
}
然后我门需要创建类的访问器SkyWorthClassVisitor
public class SkyWorthClassVisitor extends ClassVisitor implements Opcodes {
public SkyWorthClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
System.out.println("visitMethod-qhyu1"+name);
cv.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println("visitMethod-qhyu2"+name);
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
//Base类中有两个方法:无参构造以及process方法,这里不增强构造方法
if ( mv != null && name.equals("listCustomQuery") && desc.equals("(Lorg/hibernate/loader/custom/CustomQuery;Lorg/hibernate/engine/spi/QueryParameters;)Ljava/util/List;")) {
mv = new SkyWorthClassVisitor.SkyWorthVisitor(mv);
}
return mv;
}
static class SkyWorthVisitor extends MethodVisitor implements Opcodes {
public SkyWorthVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/swcares/skyworth/util/Monitor", "start", "()V",false);
// 以下是打印sql和params
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(Opcodes.ALOAD,1);
mv.visitMethodInsn(INVOKEINTERFACE, "org/hibernate/loader/custom/CustomQuery", "getSQL", "()Ljava/lang/String;", true);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "org/hibernate/engine/spi/QueryParameters", "getNamedParameters", "()Ljava/util/Map;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
|| opcode == Opcodes.ATHROW) {
visitMethodInsn(Opcodes.INVOKESTATIC, "com/swcares/skyworth/util/Monitor", "end", "()V",false);
}
mv.visitInsn(opcode);
}
}
}
并且需要创建一个线程安全的工具类,用于打印时间。
public class Monitor {
private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
public static void start() {
startTime.set(System.currentTimeMillis());
}
public static void end() {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime.get();
System.out.println("execute method use time: " + executionTime + "ms");
startTime.remove();
}
}
本文介绍了如何使用 Java Agent 和 ASM 监控方法的执行时间并打印参数。通过动态修改字节码,我们能够在应用程序运行时获取方法的执行时间和参数,并进行记录和分析。这种技术对于性能优化、调试和热点定位非常有用,为开发人员提供了更深入的应用程序分析能力。