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的观测上下文传播机制本质不同:
- WebFlux:基于Reactor响应式模型,观测上下文通过
Reactor Context自动传递,客户端请求(如WebClient)会继承当前Reactor链中的父观测,天然支持父子观测的层级关联。 - 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
相关产品推荐
相关产品推荐

