我使用 WebClient(反应器)的 Spring-WebMvc 应用程序丢失了 @SpringBootTest 中的上下文。
我有一个 @SpringBootApplication ,它向调用者提供 @RestController 端点。 当调用者请求此端点时,我的应用程序使用 WebClient 对第三方 Rest-Endpoint 进行 Rest 调用。
Web 客户端配置了两个过滤器:
过滤器需要将信息(请求和响应正文)写入ThreadLocal。 对第三方api的请求完成后,我想将线程本地的信息读取到自定义的DataStructure中并将其返回给我的api的调用者。
这在我运行应用程序时有效,但在集成测试中无效。 以下是有关应用程序的一些详细信息:
我用
Hooks.enableAutomaticContextPropagation()
我创建了一个ThreadLocalAccessor
ContextRegistry.getInstance().registerThreadLocalAccessor("THREAD_LOCAL",
InformationBasket.THREAD_LOCAL::get,
InformationBasket.THREAD_LOCAL::set,
InformationBasket.THREAD_LOCAL::remove
);
public class InformationBasket {
public static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();
}
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>context-propagation</artifactId>
<version>1.0.4</version>
<scope>compile</scope>
</dependency>
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);
});
}
@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;
}
@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
Mono.deferContextual()
你解决这个问题了吗? 我有类似的问题,在集成测试运行期间发送 Web 客户端请求时,traceId 丢失。 Hooks.enableAutomaticContextPropagation() 似乎可以解决运行应用程序时的问题,但不能解决测试问题。
这种情况在切换到 Spring Boot 3 并因此摆脱了 sleuth 后开始发生。