如何使用 bytebuddy并以javaagent方式为http请求增加header信息

15 篇文章 0 订阅
9 篇文章 0 订阅

需求

为每个http请求响应时增加header信息是哪个controller中的哪个方法返回的. 如下图:
在这里插入图片描述

提供demo

根据start.spring.io 创建一个spring-boot项目. 编写两个controller,如下图:
在这里插入图片描述

  • com.example.demo.web.controller.IndexController 内容如下
@GetMapping("")
public String hello(HttpServletRequest request, HttpServletResponse response, String name) {
    return "hello " + name;
}

编写 agent

  • 结构
    在这里插入图片描述

  • HttpAgent.java 内容

public static void premain(String agentArgs, Instrumentation inst) {
    try {
        // debug 需要暂停, 方便启动时 debug
        Thread.sleep(5000);
    } catch (Exception ignore) {
    }
    LogUtils.info("基于javaagent链路追踪");
    new AgentBuilder.Default()
            .type(
                    nameStartsWith("com.example.demo.web.controller")
            )
            .transform((builder, typeDescription, classLoader, module) -> {
                builder = builder.visit(Advice
                        .to(MyAdvice.class)
                        .on(isMethod()
                                .and(not(isConstructor()))
                                .and(not(isStatic()))
                                .and(not(isSetter()))
                                .and(not(isGetter()))
                                .and(not(isToString()))
                                .and(any())
                                .and(not(nameStartsWith("main")))));
                return builder;
            })
            .installOn(inst);
}
  • MyAdvice.java 内容

public class MyAdvice {

	// 方法执行前
    @Advice.OnMethodEnter, inline = true 必须使用默认, 否则会出错
    public static void enter(@Advice.This Object obj,
                             @Advice.AllArguments Object[] allArguments,
                             @Advice.Origin("#t") String className,
                             @Advice.Origin("#m") String methodName) {
        System.out.println(className + "." + methodName);
    }

	// 方法执行后, inline = true 必须使用默认, 否则会出错
    @Advice.OnMethodExit
    public static void exit(@Advice.This Object obj,
                            @Advice.AllArguments Object[] allArguments,
                            @Advice.Origin("#t") String className,
                            @Advice.Origin("#m") String methodName) {
        for (Object argument : allArguments) {
            if (argument instanceof HttpServletResponse) {
                HttpServletResponse response = (HttpServletResponse) argument;
                response.setHeader("t_id", UUID.randomUUID().toString());
                response.setHeader("t_c_n", className);
                response.setHeader("t_m_n", methodName);
            }
        }
    }
}

说明:

  1. MyAdvice 方法必须为 static
  2. 方法中内容, 只能在方法中编写, 不可调用其他静态方法, 比如使用 RequestContextHolder 来获取 request/response. 因为 agent 和 demo 是不同的 ClassLoader 加载的. 具体可以问度娘/谷哥
  3. 被拦截的方法中需要包含 HttpServletResponse/HttpServletRequest
  4. @Advice.OnMethodExit/@Advice.OnMethodEnter byte-buddy 注解中的属性 inline 一定是 true, 即默认. 否则同样在拦截时,会报错. 原理同第2步说明.
  5. MyAdvice 内容会在方法增强时, 被织入拦截的方法中, 增强之后的代码如下:
@GetMapping("")
public String hello(HttpServletRequest request, HttpServletResponse response, String name) {
	// MyAdvice.enter() 方法体的内容全部放在此处
    String a = "hello " + name;
    // MyAdvice.exit() 方法体的内容全部放在此处
    return a;
}

运行并测试

  • demo进行打包
  • agent进行打包, 有关javaagent相关打包内容, 请网上搜索一下.
  • 启动
java \
	// 如果需要在 ide 中 debug, 需要加上此命令
	-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 \
	// agent包的绝对路径
	-javaagent:/dist/agent-core.jar \
	-Dserver.port=7001 \
	-jar target/spring-boot-demo.jar
  • 打开浏览器 输入 http://localhost:7001, 请求并查看响应header内容
    在这里插入图片描述
    header头已经返回自定义内容

本文仅是为项目统一增加 header头提供一种思路. 并非一定要使用byte-buddy来解决. 实际项目中. 完全可以使用 sprng aop 来解决. 当然用 agent 好处是可随时插拔,完全与项目解耦.

使用 arthas 查看增强类信息

具体arthas使用方法, arthas用户文档

  1. 查看 IndexController 类是被哪个ClassLoader 加载的. 如下图, 因此agent.jar里的类是不能被此类直接使用.
    在这里插入图片描述
  2. jad 反编译看下织入结果, 红框就是 com.example.agent.interceptor.MyAdvice#enter和 com.example.agent.interceptor.MyAdvice#exit 两个方法的内容.
    在这里插入图片描述
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值