You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3 MVC中RestTemplate无法获取父观测的问题问询

Spring Boot 3 MVC中观测上下文父引用为空的问题分析与解决

问题背景

正在将部分REST应用迁移至Spring Boot 3,需求是基于请求头X-Traffic-Type的值设置额外的低基数键值对:

  • WebFlux应用实现正常:
    扩展DefaultServerRequestObservationConvention,从请求头提取值并添加到低基数键值中:
    public class MyCustomServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
    
        @Override
        public KeyValues getLowCardinalityKeyValues(final ServerRequestObservationContext context) {
            return super.getLowCardinalityKeyValues(context).and(KeyValue.of(SupplyHubMetricsAutoConfiguration.TRAFFIC_TYPE_TAG, getTrafficType(context.getCarrier())));
        }
    
        private String getTrafficType(final ServerHttpRequest request) {
            return Optional.ofNullable(request.getHeaders().getFirst("X-Traffic-Type"))
                           .map(String::toLowerCase)
                           .orElse("");
        }
    }
    
    客户端侧扩展DefaultClientRequestObservationConvention,从父观测中获取流量类型:
    public class MyCustomClientRequestObservationConvention extends DefaultClientRequestObservationConvention {
    
        @Override
        public KeyValues getLowCardinalityKeyValues(final ClientRequestObservationContext context) {
            return KeyValues.of(method(context), status(context), exception(context), outcome(context), trafficType(context), qualifier(context));
        }
    
        protected KeyValue trafficType(ClientRequestObservationContext context) {
            String trafficType = Optional.ofNullable(context.getParentObservation())
                                         .map(ObservationView::getContextView)
                                         .map(e -> e.getLowCardinalityKeyValue(TRAFFIC_TYPE_TAG))
                                         .map(KeyValue::getValue)
                                         .orElse(DEFAULT_TRAFFIC_TYPE);
            return KeyValue.of(TRAFFIC_TYPE_TAG, trafficType);
        }
    }
    
  • MVC应用存在问题:
    服务端观测能正常设置X-Traffic-Type值,但客户端扩展DefaultClientRequestObservationConvention时,context.getParentObservation()始终返回null,无法获取服务端观测作为父观测:
    public class MyCustomClientRequestObservationConvention extends DefaultClientRequestObservationConvention {
    
        @Override
        public KeyValues getLowCardinalityKeyValues(final ClientRequestObservationContext context) {
            return KeyValues.of(method(context), status(context), exception(context), outcome(context), trafficType(context), qualifier());
        }
    
        protected KeyValue trafficType(ClientRequestObservationContext context) {
            // context.getParentObservation() always returns null
            String trafficType = Optional.ofNullable(context.getParentObservation())
                                         .map(ObservationView::getContextView)
                                         .map(e -> e.getLowCardinalityKeyValue(TRAFFIC_TYPE_TAG))
                                         .map(KeyValue::getValue)
                                         .orElse(DEFAULT_TRAFFIC_TYPE);
            return KeyValue.of(TRAFFIC_TYPE_TAG, trafficType);
        }
    }
    

原因分析

WebFlux和MVC的观测上下文传播机制本质不同:

  1. WebFlux:基于Reactor响应式模型,观测上下文通过Reactor Context自动传递,客户端请求(如WebClient)会继承当前Reactor链中的父观测,天然支持父子观测的层级关联。
  2. MVC:基于Servlet同步模型,默认情况下,观测上下文不会自动传递到RestTemplate或WebClient(如果在MVC中使用)的调用流程中。Servlet线程的观测上下文仅绑定当前请求线程,客户端请求执行时如果没有手动传播上下文,就无法关联到父观测。

解决方法

方法一:手动传播观测上下文到客户端请求

通过ObservationScope将当前观测上下文绑定到客户端请求的执行线程中,以RestTemplate为例:

1. 实现观测传播拦截器

public class ObservationPropagationInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        Observation currentObservation = Observation.getCurrent();
        if (currentObservation != null) {
            // 打开观测作用域,让客户端请求继承当前观测作为父观测
            try (ObservationScope scope = currentObservation.openScope()) {
                return execution.execute(request, body);
            }
        }
        return execution.execute(request, body);
    }
}

2. 配置RestTemplate添加拦截器

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getInterceptors().add(new ObservationPropagationInterceptor());
    return restTemplate;
}

方法二:通过RequestAttributes传递流量类型(替代父观测依赖)

如果不想依赖观测层级,可以直接将从请求头获取的trafficType存入请求属性,客户端侧从属性中读取:

1. 在MVC拦截器中存储trafficType

@Component
public class TrafficTypeRequestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String trafficType = Optional.ofNullable(request.getHeader("X-Traffic-Type"))
                                     .map(String::toLowerCase)
                                     .orElse("");
        // 存入请求属性,作用域为当前请求
        request.setAttribute("trafficType", trafficType);
        return true;
    }
}

2. 修改ClientRequestObservationConvention从属性读取

protected KeyValue trafficType(ClientRequestObservationContext context) {
    String trafficType = Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                                 .map(attrs -> attrs.getAttribute("trafficType", RequestAttributes.SCOPE_REQUEST))
                                 .map(String.class::cast)
                                 .orElse(DEFAULT_TRAFFIC_TYPE);
    return KeyValue.of(TRAFFIC_TYPE_TAG, trafficType);
}

3. 注册拦截器到MVC配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private TrafficTypeRequestInterceptor trafficTypeInterceptor;

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

方法三:使用Spring Boot自动配置的观测支持(RestTemplate)

Spring Boot 3对RestTemplate有自动观测支持,确保引入spring-boot-starter-actuator依赖后,通过RestTemplateBuilder创建RestTemplate,它会自动添加观测相关的拦截器,处理上下文传播:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}

内容的提问来源于stack exchange,提问作者Rubasace

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 22:45:59