基于Javassist字节码增强实现本地公参传递

这里是weihubeats ,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

背景

测试线上的公参是通过skywalking agent的方式进行传递的。

如果是本地则会因为获取不到公参报错影响正常测试。所以需要本地也进行公参传递

正常链路是 前端 -> 网关 -> 通过skywalking进行公参处理全链路传递 -> 业务系统通过skywalking侧的sdk(TraceContext)进行公参获取

本地传递主要是两个问题

  1. 本地启动不想接入skywalking的agent启动
  2. 本地测试不走网关,公参不会存放在skywalking的上下文中

所以需要写一个简单的本地测试公参传递

目标

我们的目的很简单:

  1. 业务零侵入
  2. 能获取到公参

方案

业务本身获取公参有一个XiaoZouBaseRequest工具类。然后会提供公参的方法获取

比如

java 复制代码
public class XiaoZouBaseRequest implements Serializable {

public String getDevice() {
        return TraceContext.getCorrelation(DEVICE).orElse("");
        }
}

我们如何做到业务无感知的实现公参获取呢?

其实很简单

我们写一个最简单的拦截器。将用户请求的请求头放入一个上下文中。然后对XiaoZouBaseRequest这个类就行代理。从我们的上下文中获取公参,而不是从skywalking

实现

公参上下文

java 复制代码
public class RequestContextHolder {

    private static final TransmittableThreadLocal<Map<String, String>> requestHeaders = new TransmittableThreadLocal<>();
    
    public static void setRequestHeaders(Map<String, String> headers) {
        requestHeaders.set(headers);
    }

    public static Map<String, String> getRequestHeaders() {
        return requestHeaders.get();
    }

    public static void clear() {
        requestHeaders.remove();
    }
    
}

这里上下文的存储我们不使用ThreadLocalInheritableThreadLocal.

使用阿里transmittable-thread-local提供的TransmittableThreadLocal 可以解决线程池上线文丢失问题

需要添加依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.4</version>
</dependency>

实际如果为了简单方便也可以直接使用ThreadLocal

web拦截器

拦截器很简单实现HandlerInterceptor即可

java 复制代码
@Component
@Profile("dev")
public class LocalRequestHeaderInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, String> headers = new HashMap<>();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            headers.put(key, value);
        }
        RequestContextHolder.setRequestHeaders(headers);
        return true;
        
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        RequestContextHolder.clear();
    }
}
  1. 使用@Profile保证只在dev生效
  2. 如果是sdk的方式@Component是没用的,我们需要通过其他方式让该bean注入spring中

拦截器配置

java 复制代码
@Configuration
@Profile("dev")
public class LocalRequestMvcConfigurer implements WebMvcConfigurer {

    @Autowired
    private LocalRequestHeaderInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor);
    }
}

字节码增强XiaoZouBaseRequest

java 复制代码
@Component
@Profile("dev")
public class XiaoZouBaseRequestEnhancer implements BeanFactoryPostProcessor {

private static final String GET_DEVICE = "getDevice";

private static final String GET_DEVICE_BODY = "{\n" +
            "        return com.xiaozou.common.interceptor.RequestContextHolder.getRequestHeaders().get(com.xiaozou.common.CommonParameters.DEVICE);\n" +
            "    }";


private void enhanceBaseRequest() {
        try {
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
            CtClass cc = pool.get("com.xiaozou.common.XiaoZouBaseRequest");
            enhanceMethod(cc, GET_DEVICE, GET_DEVICE_BODY);
            cc.toClass();
            cc.detach();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        enhanceBaseRequest();
    }
    
    private void enhanceMethod(CtClass cc, String methodName, String methodBody) throws Exception {
        cc.getDeclaredMethod(methodName).setBody(methodBody);

    }


}

这里有比较多的小细节

  1. 增强XiaoZouBaseRequestEnhancer需要在XiaoZouBaseRequestEnhancer加载之前。因为如果一个类已经被类加载器加载,将无法再次修改它的字节码。所以这里使用BeanFactoryPostProcessor来扩展
  2. 在字节码增强中使用的类和方法都必须使用全类名,不然会报错java.lang.RuntimeException: javassist.CannotCompileException: [source error] no such class: RequestContextHolder

测试

写一个参数DTO

java 复制代码
public class TestDTO extends XiaoZouBaseRequest {
}

一个测试controller

java 复制代码
    @GetMapping("/test")
    public String test(TestDTO testDTO) {
    System.out.println("device = " + testDTO.getDevice());
    }

能够正常获取到公参就说明没问题

相关推荐
wstcl42 分钟前
让你的asp.net网站在调试模式下也能在局域网通过ip访问
后端·tcp/ip·asp.net
ai小鬼头8 小时前
Ollama+OpenWeb最新版0.42+0.3.35一键安装教程,轻松搞定AI模型部署
后端·架构·github
萧曵 丶9 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
老任与码9 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
华子w90892585910 小时前
基于 SpringBoot+VueJS 的农产品研究报告管理系统设计与实现
vue.js·spring boot·后端
星辰离彬10 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化
GetcharZp12 小时前
彻底告别数据焦虑!这款开源神器 RustDesk,让你自建一个比向日葵、ToDesk 更安全的远程桌面
后端·rust
jack_yin13 小时前
Telegram DeepSeek Bot 管理平台 发布啦!
后端
小码编匠13 小时前
C# 上位机开发怎么学?给自动化工程师的建议
后端·c#·.net
库森学长13 小时前
面试官:发生OOM后,JVM还能运行吗?
jvm·后端·面试