基于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());
    }

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

相关推荐
a努力。1 小时前
字节Java面试被问:系统限流的实现方式
java·开发语言·后端·面试·职场和发展·golang
小高Baby@2 小时前
使用Go语言中的Channel实现并发编程
开发语言·后端·golang
酩酊仙人3 小时前
ABP+Hangfire实现定时任务
后端·c#·asp.net·hangfire
卜锦元3 小时前
Golang后端性能优化手册(第三章:代码层面性能优化)
开发语言·数据结构·后端·算法·性能优化·golang
墨着染霜华3 小时前
Spring Boot整合Kaptcha生成图片验证码:新手避坑指南+实战优化
java·spring boot·后端
czlczl200209253 小时前
Spring Security @PreAuthorize 与自定义 @ss.hasPermission 权限控制
java·后端·spring
老华带你飞4 小时前
考试管理系统|基于java+ vue考试管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
2501_921649494 小时前
股票 API 对接,接入美国纳斯达克交易所(Nasdaq)实现缠论回测
开发语言·后端·python·websocket·金融
Grassto4 小时前
从 GOPATH 到 Go Module:Go 依赖管理机制的演进
开发语言·后端·golang·go
xl-xueling4 小时前
从快手直播故障,看全景式业务监控势在必行!
大数据·后端·网络安全·流式计算