上下文传播在@SpringBootTest中不起作用

问题描述 投票:0回答:1

我使用 WebClient(反应器)的 Spring-WebMvc 应用程序丢失了 @SpringBootTest 中的上下文。

我有一个 @SpringBootApplication ,它向调用者提供 @RestController 端点。 当调用者请求此端点时,我的应用程序使用 WebClient 对第三方 Rest-Endpoint 进行 Rest 调用。

Web 客户端配置了两个过滤器:

  1. ExchangeFilterFunction.of请求处理器
  2. ExchangeFilterFunction.of响应处理器

过滤器需要将信息(请求和响应正文)写入ThreadLocal。 对第三方api的请求完成后,我想将线程本地的信息读取到自定义的DataStructure中并将其返回给我的api的调用者。

这在我运行应用程序时有效,但在集成测试中无效。 以下是有关应用程序的一些详细信息:

  1. 我用

    Hooks.enableAutomaticContextPropagation()

  2. 我创建了一个ThreadLocalAccessor

ContextRegistry.getInstance().registerThreadLocalAccessor("THREAD_LOCAL",
    InformationBasket.THREAD_LOCAL::get,
    InformationBasket.THREAD_LOCAL::set,
    InformationBasket.THREAD_LOCAL::remove
);
  1. ThreadLocal 被“包装”在一个简单的类中:
public class InformationBasket {
    public static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();
}
  1. 我对类路径有上下文传播依赖:
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>context-propagation</artifactId>
  <version>1.0.4</version>
  <scope>compile</scope>
</dependency>
  1. 网络客户端的过滤器如下所示:

    private ExchangeFilterFunction getRequestFilter() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            final ThreadLocal<Map<String, String>> threadLocal = InformationBasket.THREAD_LOCAL;
            final Map<String, String> arg = threadLocal.get();
            log.info("--> ThreadLocal in RequestFilter: {}", threadLocal);
            log.info("--> Map in ThreadLocal in RequestFilter: {}", arg);
            arg.put("Request", "some-request-data");
            return Mono.just(clientRequest);
        });
    }
    
    private ExchangeFilterFunction getResponseFilter() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            final ThreadLocal<Map<String, String>> threadLocal = InformationBasket.THREAD_LOCAL;
            final Map<String, String> arg = threadLocal.get();
            log.info("<-- ThreadLocal in ResponseFilter: {}", threadLocal);
            log.info("<-- Map in ThreadLocal in ResponseFilter: {}", arg);
            arg.put("Response", "some-response-data");
            return Mono.just(clientResponse);
        });
    }
  1. RestController 看起来像这样:

    @GetMapping("/simple")
    public String simpleEndpoint() {
    
        InformationBasket.THREAD_LOCAL.set(new HashMap<>());
        final ThreadLocal<Map<String, String>> threadLocal = InformationBasket.THREAD_LOCAL;
        log.info("ThreadLocal in Endpoint: {}", threadLocal);
    
        final URI uri = UriComponentsBuilder.fromHttpUrl(url).path("3rd/").path("order").build().toUri();
    
        final String response = webClient.get()
                .uri(uri)
                .retrieve()
                .toEntity(String.class)
                .mapNotNull(HttpEntity::getBody)
                .block();
    
        // We expect that the Map of the ThreadLocal contains data from both filters (request and response)
        InformationBasket.THREAD_LOCAL.get().forEach((key, value) -> {
            log.info("Key: {} Value: {}", key, value);
        });
    
        return response;
    }
  1. 集成测试如下所示:

@SpringBootTest(classes = ReactorTestingApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SimpleControllerTest {

    @LocalServerPort
    int port;
    
    @Autowired
    private WebTestClient client;
    
    @Test
    void simpleEndpoint_success() throws Exception {
        client.get().uri("/simple")
                .exchange()
                .expectStatus()
                .isOk();
    }

}

测试中的日志如下所示:


2023-09-11T12:30:53.265+02:00  INFO 16745 --- \[o-auto-1-exec-1\] c.e.reactortesting.SimpleController      : ThreadLocal in Endpoint: java.lang.ThreadLocal@49f16008
2023-09-11T12:30:53.271+02:00  INFO 16745 --- \[o-auto-1-exec-1\] c.e.reactortesting.SimpleController      : --\> ThreadLocal in RequestFilter: java.lang.ThreadLocal@49f16008
2023-09-11T12:30:53.272+02:00  INFO 16745 --- \[o-auto-1-exec-1\] c.e.reactortesting.SimpleController      : --\> Map in ThreadLocal in RequestFilter: {}

------

\--\> Exception: java.lang.NullPointerException: Cannot invoke "java.util.Map.put(Object, Object)" because "arg" is null

正在运行的应用程序中的日志如下所示:


2023-09-11T12:33:42.083+02:00  INFO 16902 --- \[nio-8080-exec-2\] c.e.reactortesting.SimpleController      : ThreadLocal in Endpoint: java.lang.ThreadLocal@771db298
2023-09-11T12:33:42.092+02:00  INFO 16902 --- \[nio-8080-exec-2\] c.e.reactortesting.SimpleController      : --\> ThreadLocal in RequestFilter: java.lang.ThreadLocal@771db298
2023-09-11T12:33:42.092+02:00  INFO 16902 --- \[nio-8080-exec-2\] c.e.reactortesting.SimpleController      : --\> Map in ThreadLocal in RequestFilter: {}
2023-09-11T12:33:42.409+02:00  INFO 16902 --- \[ctor-http-nio-2\] c.e.reactortesting.SimpleController      : \<-- ThreadLocal in ResponseFilter: java.lang.ThreadLocal@771db298
2023-09-11T12:33:42.409+02:00  INFO 16902 --- \[ctor-http-nio-2\] c.e.reactortesting.SimpleController      : \<-- Map in ThreadLocal in ResponseFilter: {Request=some-request-data}
2023-09-11T12:33:42.418+02:00  INFO 16902 --- \[nio-8080-exec-2\] c.e.reactortesting.SimpleController      : Key: Response Value: some-response-data
2023-09-11T12:33:42.418+02:00  INFO 16902 --- \[nio-8080-exec-2\] c.e.reactortesting.SimpleController      : Key: Request Value: some-request-data

如您所见,应用程序在集成测试中的行为有所不同。是什么导致了这种不同的行为?

提前谢谢您。 最好的,J

  • 尝试使用MockMvc、WebTestClient
  • 尝试使用不同版本的上下文传播库
  • 在过滤功能中尝试过
    Mono.deferContextual()
project-reactor spring-boot-test thread-local micrometer
1个回答
0
投票

你解决这个问题了吗? 我有类似的问题,在集成测试运行期间发送 Web 客户端请求时,traceId 丢失。 Hooks.enableAutomaticContextPropagation() 似乎可以解决运行应用程序时的问题,但不能解决测试问题。

这种情况在切换到 Spring Boot 3 并因此摆脱了 sleuth 后开始发生。

© www.soinside.com 2019 - 2024. All rights reserved.