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

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

相关推荐
moisture4 小时前
CUDA常规知识点
后端·面试
用户298698530144 小时前
Java 使用 Spire.PDF 将PDF文档转换为Word格式
java·后端
Reboot4 小时前
使用cloc统计代码行数
后端
neoooo4 小时前
当域名遇上家里的电脑:一条隧道通向世界
后端
zjjuejin4 小时前
Maven依赖管理艺术
后端·maven
RoyLin4 小时前
TypeScript设计模式:复合模式
前端·后端·typescript
我的小月月4 小时前
SQLFE:网页版数据库(VUE3+Node.js)
前端·后端
Alan521594 小时前
Java 后端实现基于 JWT 的用户认证和权限校验(含代码讲解)
前端·后端
RoyLin4 小时前
TypeScript设计模式:策略模式
前端·后端·typescript
brzhang4 小时前
为什么说低代码谎言的破灭,是AI原生开发的起点?
前端·后端·架构