Wow4j Wow4j
首页
个人使用说明书
后端开发
前端开发
测试开发
运维开发
大数据开发
产品&UI交互
团队管理
软技能
他山之石
开源产品
敬请期待
GitHub (opens new window)
首页
个人使用说明书
后端开发
前端开发
测试开发
运维开发
大数据开发
产品&UI交互
团队管理
软技能
他山之石
开源产品
敬请期待
GitHub (opens new window)
  • 概要
  • 面试八股文

  • 服务端小技巧合集

    • byte buddy 实现链路上所有方法耗时打印
      • 1. 需求背景
      • 2. 实现思路
      • 3. 基于 bytebuddy 实现
        • 3.1 maven 项目java-agent-demo
        • 3.2 项目 java-agent-user 来进行验证
        • 3.3 在工程 java-agent-user 中执行命令
        • 3.4 执行效果
      • 4. 总结
    • 业务线程池不丢 traceId 的方法
    • 让你的java业务代码并发的调用,并正确的处理返回结果
    • 服务端常见线上问题整理与解决措施
    • 服务端日志打印最佳实践
    • 轻松正确理解并上手RESTful
    • 服务端业务线程池优雅使用
    • 服务端如何正确优雅使用流控平台
    • 服务端如何正确的使用分布式锁防止缓存击穿
    • 服务端接口设计最佳实践
  • Java基础

  • MySQL 相关

  • Redis 最佳实践指南

  • 文本搜索Elasticsearch

  • Kafka 最佳实践指南

  • 网络相关

  • 架构相关

  • 监控告警

  • 防爬风控

  • 稳定性 checklist

  • 效能工具

  • 后端开发
  • 服务端小技巧合集
timchen525
2022-03-26

byte buddy 实现链路上所有方法耗时打印

# 1. 需求背景

业务上需要实现对指定包路径下,所有方法的请求耗时进行打印,要求尽量不要与核心业务耦合。

# 2. 实现思路

如果想实现对业务尽量无感,且打印每个方法的耗时,先排除掉:在业务中直接进行日志打印耗时。主要实现思路有:

  1. 利用 AOP 切面来实现
  2. 利用字节码 agent 来实现

由于网络上对于使用 AOP 切面来实现该功能的例子比较多,这里重点介绍下如何利用 字节码 agent 来实现,具体的字节码相关的,用的是 bytebuddy 来实现。

# 3. 基于 bytebuddy 实现

基于 bytebuddy 实现的步骤:

# 3.1 maven 项目java-agent-demo

  1. 项目的 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">

    <groupId>com.tim</groupId>
    <version>1.0.0-SNAPSHOT</version>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>java-agent-demo</artifactId>

    <dependencies>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.9.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <!-- 自动添加 META-INF/MAINFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>PreMainAgent</Premain-Class>
                            <Agent-Class>PreMainAgent</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>
  1. 新增一个类 PreMainAgent.java,里面添加静态方法 premain
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.lang.instrument.Instrumentation;

public class PreMainAgent {

    // 定义要拦截的包路径前缀
    private static final String PACKAGE_NAME_PREFIX = "com.agent";

    public static void premain(String agentparam, Instrumentation inst) {
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer(){
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
                // method 指定哪些方法需要被拦截,ElementMatchers.any 指定了所有的方法
                // 声明 intercept 拦截器
                return builder.method(ElementMatchers.<MethodDescription>any())
                        .intercept(MethodDelegation.to(MyInterceptor.class));
            }
        };

        // type 指定了 agent 拦截的包名,以com.agent 作为前缀
        // 指定了 transformer
        // 将配置安装到 Instrumentation
        new AgentBuilder.Default().type(ElementMatchers.<TypeDescription>nameStartsWith("com.agent")).transform(transformer).installOn(inst);
    }
}
  1. 新增一个拦截类 MyInterceptor.java
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class MyInterceptor {

    @RuntimeType
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        Long start = System.currentTimeMillis();
        try {
            return callable.call();
        } finally {
            System.out.println(method.getName() + ":" + (System.currentTimeMillis() - start) + "ms");
        }
    }
}
  1. 执行 mvn clean、mvn compile、mvn deploy 或者通过 idea中的maven ui界面进行操作

    在 target 下生成一个 java-agent-demo-1.0.0-SNAPSHOT.jar

# 3.2 项目 java-agent-user 来进行验证

  1. 新建一个普通的 java 项目 java-agent-user
  2. 在包路径 com.agent 下新建一个Main类下,代码如下:
package com.agent;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("main 方法执行");
        Thread.sleep(1000L);
        hello();
        Thread.sleep(10L);
        hello2();
    }

    public static void hello() throws InterruptedException {
        Thread.sleep(50L);
        System.out.println("23");
    }

    public static void hello2() throws InterruptedException {
        Thread.sleep(10L);
        System.out.println("33");
    }
}

# 3.3 在工程 java-agent-user 中执行命令

java -javaagent:/${路径}/java-agent-demo-1.0.0-SNAPSHOT.jar -jar java-agent-user.jar

或者直接在idea的VM options中执行如下命令:

-javaagent:/%{路径}/java-agent-demo-1.0.0-SNAPSHOT.jar

说明:${路径} 请换成实际打成 agent 的包路径。

# 3.4 执行效果

main 方法执行
23
hello:52ms
33
hello2:12ms
main:1078ms

# 4. 总结

利用 bytebuddy 可以轻松的实现字节码的插砖技术,实现业务解耦打印每个方法的耗时。

上次更新: 2023/01/31, 09:21:58
草稿
业务线程池不丢 traceId 的方法

← 草稿 业务线程池不丢 traceId 的方法→

Theme by Vdoing | Copyright © 2022-2023 timchen525 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×