简介

Java Agent 是一种强大的工具,它允许我们在 Java 程序运行时修改字节码并注入自定义逻辑。结合 ASM(Java 字节码操作库),我们可以编写一个 Java Agent,用于监控方法的执行时间并打印方法参数。这种技术对于性能分析和调试非常有用。
ASM(原名"ANALYZER, SCANNER and MODEL")是一个流行的 Java 字节码操作库,它提供了强大的功能来读取、修改和生成字节码。ASM 可以直接操作字节码,而无需依赖于源代码或反编译。
ASM字节码插桩探索这篇文章也可以先了解一下。

以下是对 ASM 的介绍:

  1. 功能丰富:ASM 提供了广泛的功能和 API,可以对字节码进行细粒度的操作。它支持读取和分析现有的字节码,修改字节码并生成新的字节码。ASM 还提供了访问器(Visitor)模式,使得开发人员可以在扫描字节码时添加自定义的处理逻辑。
  2. 快速高效:ASM 被设计为快速和高效的字节码操作库。它使用了一些优化技术来提高性能,并且具有较低的内存占用。由于它直接操作字节码,相对于其他基于源代码的操作库,ASM 在性能上具有一定的优势。
  3. 广泛应用:ASM 在 Java 生态系统中得到广泛的应用。它被用于各种场景,包括字节码增强、动态代理、AOP(面向切面编程)、代码生成、静态分析、代码优化等。许多框架和工具,如 Spring、Hibernate、JUnit 等,都使用了 ASM 来实现字节码级的功能。

Java Agent 是一个独立的 Java 程序,可以在应用程序启动时动态地修改字节码。它利用了 Java 虚拟机的 Instrumentation API,通过字节码转换技术来修改和增强正在运行的程序。

以下是对 Java Agent 的介绍:

  1. 动态修改字节码:Java Agent 允许开发人员在应用程序运行时动态地修改字节码。它可以在类加载过程中拦截和转换字节码,并注入自定义的逻辑。这使得开发人员可以在运行时对应用程序的行为进行增强和定制。
  2. 监控和诊断:Java Agent 可以用于监控和诊断应用程序的性能和行为。通过在字节码中插入监控代码,可以测量方法的执行时间、内存使用情况等。这对于性能分析、热点定位和优化非常有用。
  3. AOP 和代码生成:Java Agent 可以实现 AOP(面向切面编程)的功能,通过在字节码中插入切面逻辑来实现横切关注点。它还可以用于动态生成代码,例如动态代理、动态子类等。
  4. 类加载器级别的增强:Java Agent 在类加载器级别操作字节码,因此可以对整个应用程序进行增强,包括第三方库和框架。这使得开发人员能够在不修改源代码的情况下对应用程序进行定制。
  5. 应用场景广泛: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启动类

创建一个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("-----------------启动结束---------------------");
    }

}

创建Transformer

我的项目使用的是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

创建类的访问器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启动命令如何让打印级别调整为debug级别 java打印方法执行时间_Java


打印出执行时间

java启动命令如何让打印级别调整为debug级别 java打印方法执行时间_Java_02

总结

本文介绍了如何使用 Java Agent 和 ASM 监控方法的执行时间并打印参数。通过动态修改字节码,我们能够在应用程序运行时获取方法的执行时间和参数,并进行记录和分析。这种技术对于性能优化、调试和热点定位非常有用,为开发人员提供了更深入的应用程序分析能力。