spring - 在 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/阻塞应用程序示例。唯一的反应部分是WebClient
和ExchangeFilterFunction
。RestTemplate
处于维护模式,建议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 中做出的最佳假设
任何帮助/指导表示赞赏。
解决方案
推荐阅读
- javascript - 等待请求完成后再返回
- python - 如何再次在 tkinter 中打开初始窗口?
- sql - 在按钮上单击附加 OR... 在 SQL 查询中
- javascript - 允许用户上传带有裁剪、尺寸和定位的图像
- swift - Swift 应用程序没有版本?
- react-native - 动画滚动视图中的 scrollHandler 和 scrollTo
- r - 创建函数以计算 R 中组内的出现次数
- spring-boot - Spring Cloud Stream 中的生产者回调与反应堆核心发布者
- mlflow - 使用 mflow 客户端有什么好处?
- tkinter - 通过 tkinter 中的事件传递值