第一次反射实践

 

使用方式

连续使用两次反射 (1)在已经得到具体实例的情况下,使用反射获取其类型,然后获取其指定字段(该字段为引用类型,指向另一个类) (2)根据上一步得到的字段,继续通过反射调用其指定方法    

代码

package com.example.gatewaydemo.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @description 获取skywalking的traceId,并将其传递给nginx
 */
@Slf4j
@Component
public class TraceFilter implements GlobalFilter, Ordered {

    static final Logger logger = LogManager.getLogger(TraceFilter.class);

    /**
     * @description 设置优先级为最低
     */
    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    /**
     * @description 获取traceId并将其添加到响应头中
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            // 获取skywalking设置到exchange中的span实例,并使用反射获取其owner字段(该字段从父类的父类继承而来,类型为TracingContext)
            Object entrySpanInstance = exchange.getAttributes().get("SKYWALING_SPAN");
            Class<?> entrySpanClazz = entrySpanInstance.getClass().getSuperclass().getSuperclass();
            Field field = entrySpanClazz.getDeclaredField("owner");
            // 利用反射调用owner的getReadablePrimaryTraceId()方法,以获取traceId
            field.setAccessible(true);
            Object ownerInstance = field.get(entrySpanInstance);
            Class<?> ownerClazz = ownerInstance.getClass();
            Method getTraceId = ownerClazz.getMethod("getReadablePrimaryTraceId");
            String traceId = (String) getTraceId.invoke(ownerInstance);
            // 将traceId添加到响应头中
            exchange.getResponse().getHeaders().add("tid", traceId);
        } catch (Exception exception) {
            logger.warn("an exception occurred while fetching skywalking traceId", exception);
        }
        return chain.filter(exchange);
    }

}
反射获取的对象说明: exchange.getAttributes().get("SKYWALING_SPAN"); 得到的实例类型是 EntrySpan,EntrySpan 的父类是 StackBasedTracingSpan,StackBasedTracingSpan 的父类是 AbstractTracingSpan AbstractTracingSpan 有一个字段 owner,这个字段是 TracingContext 类型的,TracingContext 类有一个成员方法 getReadablePrimaryTraceId(),可以获取到我们需要的 traceId  

任务简述

任务背景

使用了 Skywalking 作为调用轨迹跟踪工具,每一个轨迹都有一个轨迹 id,可以根据该 id 在 Skywalking 的轨迹页面搜索具体的轨迹。 使用的框架是 SpringCloud,使用 SpringCloudGateway 作为网关,然后使用了 Nginx 作为 gateway 的上层,即,外部请求最先打到Nginx,然后由 Nginx 转发到 gateway,最后由 gateway 转发给具体的服务。 把 gateway 作为 Skywalking 的监控入口,即,Nginx 不包含在内。 做了一个 Nginx 的请求超时报警功能,即对于耗时过长的请求,会将 Nginx 的 access 日志推送出来。

任务内容

把每个请求的轨迹 id,由 gateway 反向传送至 Nginx,并且配置到 Nginx 的 access 日志中。 基本实现思路是,在 gateway 中获取到轨迹 id,然后使用过滤器将其设置到响应头中,传送给 Nginx。    

Skywalking(8.4版本)相关源码

怎么找到的相关源码? 首先,在了解 Skywalking 的 agent 原理的时候,知道是使用了java的探针技术,这种技术涉及到一种类,即 *Instrumentation.java 类,然后,了解到 SpringcloudGateway 又是基于 Webflux,于是下载 Skywalking 的源码,找到相关模块。 第一次反射实践

打开 DispatcherHandlerInstrumentation 类,根据以下代码可知,使用了 DispatcherHandlerHandleMethodInterceptor 这个拦截器对被拦截(或者说增强)的类进行了增强

    @Override
    public String getMethodsInterceptor() {
        return "org.apache.skywalking.apm.plugin.spring.webflux.v5.DispatcherHandlerHandleMethodInterceptor";
    }

打开 DispatcherHandlerHandleMethodInterceptor 类,看到以下代码,发现把 span (注:在 Skywalking 的轨迹中,span 是最小数据单元)注入到了 exchange 的 attributes 中(键为 SKYWALING_SPAN)

exchange.getAttributes().put("SKYWALING_SPAN", span);

而 exchange 在 gateway 的过滤器方法中是第一个参数

filter(ServerWebExchange exchange, GatewayFilterChain chain)

于是,在把环境准备好之后,使用 debug 模式探寻 exchange 中的数据,发现了我们需要的 traceId

第一次反射实践

同时,我们可以看到键 SKYWALING_SPAN 对应的值的类型是 EntrySpan,以及,最终的数据在 EntrySpan 的 owner 字段中,并且该 owner 字段是 TracingContext 类型的

接着,回到 Skywalking 的源码,找到 EntrySpan,发现其有一个构造器中包含 owner 字段
    public EntrySpan(int spanId, int parentSpanId, String operationName, TracingContext owner) {
        super(spanId, parentSpanId, operationName, owner);
        this.currentMaxDepth = 0;
    }

于是点击该 super 方法进入其父类,同样看到了一个构造器

    protected StackBasedTracingSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) {
        super(spanId, parentSpanId, operationName, owner);
        this.stackDepth = 0;
        this.peer = null;
    }

继续点击 super 方法,可以看到 EntrySpan 的 owner 属性原来是从其父类的父类 AbstractTracingSpan 中继承而来

    protected AbstractTracingSpan(int spanId, int parentSpanId, String operationName, TracingContext owner) {
        this.operationName = operationName;
        this.spanId = spanId;
        this.parentSpanId = parentSpanId;
        this.owner = owner;
    }

进入 owner 所属的类 TracingContext 中,发现其中有一个方法可能跟我们需要的 traceId 有关

第一次反射实践

于是尝试在代码中使用反射调用此方法,结果发现该方法获取的就是 traceId



总结

这是在项目中第一次实际使用反射,以前对反射的理解都停留在理论上。 一般网上给出的示例是,根据全类名获取 Class,然后创建一个实例,接着获取字段或者调用方法。 这次使用反射是经过组长的提示才用的,在此之前,自己也想过用反射,但想想自己学的那些示例,觉得好像无法直接套用。因为网上各种示例中,一般都是根据 Class 创建实例,我这里是已经有实例了,感觉好像用不了反射了。(以为反射只可以根据类创建实例,不知道反射可以直接使用已存在的实例) 在组长表示有可行性的时候,我进行了尝试。反复翻阅博客,看各种用法,联系到项目中的实际情况,在看了好一会儿之后,突然意识到,各种反射示例中的 Class 可以根据已有的实例获取,至于 instance,就直接使用获取到的那个实例就可以了。 程序员还是要多多实践,才能加深对理论的理解啊。
上一篇:Vue实现长按二维码保存到本地


下一篇:Scrum实施过程中曾遇到的那些“坑”