赞
踩
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
<?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>SQLQueryAgent</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-bom</artifactId> <version>9.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-tree</artifactId> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency> </dependencies> <build> <plugins> <!-- 使用 maven-shade-plugin 插件打包 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <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.*.*.StartUp</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </transformer> </transformers> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </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();
}
}
打印出sql和sql的参数
打印出执行时间
本文介绍了如何使用 Java Agent 和 ASM 监控方法的执行时间并打印参数。通过动态修改字节码,我们能够在应用程序运行时获取方法的执行时间和参数,并进行记录和分析。这种技术对于性能优化、调试和热点定位非常有用,为开发人员提供了更深入的应用程序分析能力。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。