首页 > 解决方案 > 在 WebClient 的 ExchangeFilterFunction 中使用 RequestContextHolder

问题描述

背景:我有传统的 Spring MVC 应用程序(Spring Boot),它利用 Spring Cloud OpenFeign(REST 服务消费)和 Apache CXF(SOAP 服务消费)。我正在尝试查看是否可以将这两个 HTTP 客户端合并到 Spring 的WebClient.


我试图弄清楚是否有更好的方法来引用放置在其中的属性RequestContextHolder,然后在其中使用/使用它们ExchangeFilterFunction(替换各种 Feign/CXF 请求拦截器)。

例如,以下工作:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.requireNonNull;
@SpringBootApplication
public class DemoApplication {

    private static Log logger = LogFactory.getLog(DemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    RouterFunction<ServerResponse> router(WebClient.Builder webClientBuilder) {
        WebClient webClient = webClientBuilder.baseUrl("https://jsonplaceholder.typicode.com").filter((req, next) -> {
            Object foo = requireNonNull(RequestContextHolder.getRequestAttributes()).getAttribute("foo", WebRequest.SCOPE_REQUEST);
            logger.info("Foo=" + foo);
            return next.exchange(ClientRequest.from(req).header("foo", foo.toString()).build());
        }).build();

        return RouterFunctions.route()
                .before((request) -> {
                    logger.info("Setting foo from " + Thread.currentThread().getName());
                    RequestContextHolder.getRequestAttributes().setAttribute("foo", "bar", WebRequest.SCOPE_REQUEST);
                    return ServerRequest.from(request).build();
                })
                .GET("/test", (request) -> {
                    ClientResponse response = webClient.get().uri("/todos/1").exchange().block();

                    Map<String, Object> body = new HashMap<>();
                    body.put("attributes", RequestContextHolder.getRequestAttributes().getAttributeNames(WebRequest.SCOPE_REQUEST));
                    body.put("todo", response.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}).block());

                    return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(body);
                })
                .build();
    }
}

请注意,上面仍然是一个 servlet/阻塞应用程序示例。唯一的反应部分是WebClientExchangeFilterFunctionRestTemplate处于维护模式,建议WebClient按照javadoc使用

当我使用多个请求向端点发送垃圾邮件时,它似乎有效:

2020-08-24 21:26:22.829  INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-1
2020-08-24 21:26:22.829  INFO 15212 --- [nio-8080-exec-1] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:22.915  INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-2
2020-08-24 21:26:22.916  INFO 15212 --- [nio-8080-exec-2] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:23.011  INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-3
2020-08-24 21:26:23.011  INFO 15212 --- [nio-8080-exec-3] com.example.demo.DemoApplication         : Foo=bar
2020-08-24 21:26:23.118  INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication         : Setting foo from http-nio-8080-exec-4
2020-08-24 21:26:23.119  INFO 15212 --- [nio-8080-exec-4] com.example.demo.DemoApplication         : Foo=bar

然而,这样做感觉不对,因为 Reactor 提供了Context并且更是如此,因为这个 Reactor FAQ明确地调用了以下内容(强调我的):

现在我们已经确定 MDC “正常工作”并不是在声明式 API 中做出的最佳假设

任何帮助/指导表示赞赏。

标签: springspring-bootspring-webfluxproject-reactor

解决方案


推荐阅读