【Agent】ByteBuddy 增加方法执行耗时

1  前言

上节我们使用javassist字节码增强的方式,来监控方法程序的执行耗时。这节我们再用一个字节码操作工具ByteBuddy来帮助我们实现更完善的监控程序。

2  Java agent

2.1  ByteBuddy 是什么

Byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。

除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。

此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类。

无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。

已支持Java 11,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。

就像它的官网介绍;

Byte Buddy 是一个代码生成和操作库,用于在 Java 应用程序运行时创建和修改 Java 类,而无需编译器的帮助。除了 Java 类库附带的代码生成实用程序外,Byte Buddy 还允许创建任意类,并且不限于实现用于创建运行时代理的接口。

此外,Byte Buddy 提供了一种方便的 API,可以使用 Java 代理或在构建过程中手动更改类。

    • 无需理解字节码指令,即可使用简单的 API 就能很容易操作字节码,控制类和方法。
    • 已支持Java 11,库轻量,仅取决于Java字节代码解析器库ASM的访问者API,它本身不需要任何其他依赖项。
    • 比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。

 另外关于字节码的增强工具的对比:

对比ASMJavassistJDK ProxyCglibByteBuddy
起源时间 2002 1999 2000 2011 2014
包大小 130KB (版本9.3) 788KB (版本3.28.0-GA)     3.7MB (版本1.10.19)
增强方式 字节码指令 字节码指令和源码(注:源码文本) 源码 源码 源码
源码编译 NA 不支持 支持 支持 支持
agent支持 支持 支持 不支持,依赖框架 不支持,依赖框架 支持
性能
维护状态 停止升级 停止维护 活跃
优点 超高性能,应用场景广泛 同时支持字节码指令和源码两种增强方式 JDK原生类库支持   零侵入,提供良好的API扩展编程
缺点 字节码指令对应用开发者不友好   场景非常局限,只适用于Java接口 已经不再维护,对于新版JDK17+支持不好,官网建议切换到ByteBuddy  
应用场景 小,高性能,广泛用于语言级别     广泛用于框架场景 广泛用于Trace场景

 

2.2  尝试

首先加入依赖:

<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>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <artifactSet>
            <includes>
                <include>javassist:javassist:jar:</include>
                <include>net.bytebuddy:byte-buddy:jar:</include>
                <include>net.bytebuddy:byte-buddy-agent:jar:</include>
            </includes>
        </artifactSet>
    </configuration>
</plugin>

MyAgent.java

/**
 * @author: kuku
 * @description
 */
public class MyAgent {

    /**
     * JVM 首先尝试在代理类上调用以下方法
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
            return 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.virtuous")) // 指定需要拦截的类
                .transform(transformer)
                .with(listener)
                .installOn(inst);
    }

    /**
     * 如果代理类没有实现上面的方法,那么 JVM 将尝试调用该方法
     * @param agentArgs
     */
    public static void premain(String agentArgs) {
        System.out.println("premain方法2" + agentArgs);
    }

}

MethodCostTime.java

/**
 * @author kuku
 */
public class MethodCostTime {

    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
        long start = System.nanoTime();
        try {
            // 原有函数执行
            return callable.call();
        } finally {
            System.out.println(method + " 方法耗时: " + (System.nanoTime() - start) + "ns");
        }
    }

}

测试执行效果:

3  小结

好啦,本节就到这里,有理解不对的地方欢迎指正哈。

posted @ 2024-06-06 08:38  酷酷-  阅读(464)  评论(0)    收藏  举报