说明
在 Spring boot web 中我们可以通过 RequestContextHolder 很方便的获取 request。
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 获取 requestHttpServletRequest request = requestAttributes.getRequest();
不再需要通过参数传递 request。在 Spring webflux 中并没提供该功能,使得我们在 Aop 或者一些其他的场景中获取 request 变成了一个奢望???
寻求解决方案
首先我想到的是看看 spring-security 中是否有对于的解决方案,因为在 spring-security 中我们也是可以通过 SecurityContextHolder 很方便快捷的获取当前登录的用户信息。
找到了 ReactorContextWebFilter,我们来看看 security 中他是怎么实现的。
https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/server/context/ReactorContextWebFilter.java#L43
public class ReactorContextWebFilter implements WebFilter {private final ServerSecurityContextRepository repository;public ReactorContextWebFilter(ServerSecurityContextRepository repository) {Assert.notNull(repository, "repository cannot be null");this.repository = repository;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {return chain.filter(exchange).subscriberContext(c -> c.hasKey(SecurityContext.class) ? c :withSecurityContext(c, exchange));}private Context withSecurityContext(Context mainContext, ServerWebExchange exchange) {return mainContext.putAll(this.repository.load(exchange).as(ReactiveSecurityContextHolder::withSecurityContext));}}
源码里面我们可以看到 他利用一个 Filter,chain.filter(exchange) 的返回值 Mono 调用了 subscriberContext 方法。
那么我们就去了解一下这个 reactor.util.context.Context。找到 reactor 官方文档中的 context 章节:https://projectreactor.io/docs/core/release/reference/#context
大意是:从 Reactor 3.1.0 开始提供了一个高级功能,可以与 ThreadLocal 媲美,应用于 Flux 和 Mono 的上下文工具 Context。更多请大家查阅官方文档,对英文比较抵触的朋友可以使用 google 翻译。
mica 中的实现
mica 中的实现比较简单,首先是我们的 ReactiveRequestContextFilter:
/*** ReactiveRequestContextFilter** @author L.cm*/@Configuration@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)public class ReactiveRequestContextFilter implements WebFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {ServerHttpRequest request = exchange.getRequest();return chain.filter(exchange).subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));}}
在 Filter 中直接将 request 存储到 Context 上下文中。
ReactiveRequestContextHolder 工具:
/*** ReactiveRequestContextHolder** @author L.cm*/public class ReactiveRequestContextHolder {static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;/*** Gets the {@code Mono<ServerHttpRequest>} from Reactor {@link Context}* @return the {@code Mono<ServerHttpRequest>}*/public static Mono<ServerHttpRequest> getRequest() {return Mono.subscriberContext().map(ctx -> ctx.get(CONTEXT_KEY));}}
怎么使用呢?
mica 中对未知异常处理,从 request 中获取请求的相关信息
@ExceptionHandler(Throwable.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Mono<?> handleError(Throwable e) {log.error("未知异常", e);// 发送:未知异常异常事件return ReactiveRequestContextHolder.getRequest().doOnSuccess(r -> publishEvent(r, e)).flatMap(r -> Mono.just(R.fail(SystemCode.FAILURE)));}private void publishEvent(ServerHttpRequest request, Throwable error) {// 具体业务逻辑}
WebClient 透传 request 中的 header
此示例来源于开源中国问答中笔者的回复: 《如何在gateway 中获取 webflux的 RequestContextHolder》
@GetMapping("/test")@ResponseBodypublic Mono<String> test() {WebClient webClient = testClient();return webClient.get().uri("").retrieve().bodyToMono(String.class);}@Beanpublic WebClient testClient() {return WebClient.builder().filter(testFilterFunction()).baseUrl("https://www.baidu.com").build();}private ExchangeFilterFunction testFilterFunction() {return (request, next) -> ReactiveRequestContextHolder.getRequest().flatMap(r -> {ClientRequest clientRequest = ClientRequest.from(request).headers(headers -> headers.set(HttpHeaders.USER_AGENT, r.getHeaders().getFirst(HttpHeaders.USER_AGENT))).build();return next.exchange(clientRequest);});}
上段代码是透传 web 中的 request 中的 user_agent 请求头到 WebClient 中。
开源推荐
micaSpring boot 微服务核心组件集:https://gitee.com/596392912/micaAvue一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avuepig宇宙最强微服务(架构师必备):https://gitee.com/log4j/pigSpringBlade完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBladeIJPay支付SDK让支付触手可及:https://gitee.com/javen205/IJPay
关注我们

扫描上面二维码,更多精彩内容每天推荐!
转载声明
如梦技术对此篇文章有最终所有权,转载请注明出处,参考也请注明,谢谢!
