侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130557 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

Spring boot webflux 中实现 RequestContextHolder

2023-12-14 星期四 / 0 评论 / 0 点赞 / 8 阅读 / 6482 字

说明在 Spring boot web 中我们可以通过 RequestContextHolder 很方便的获取 request。ServletRequestAttributes requestAttr

说明

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;	}	@Override	public 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 {	@Override	public 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 中。

开源推荐

  • mica Spring boot 微服务核心组件集:https://gitee.com/596392912/mica
  • Avue 一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avue
  • pig 宇宙最强微服务(架构师必备):https://gitee.com/log4j/pig
  • SpringBlade 完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBlade
  • IJPay 支付SDK让支付触手可及:https://gitee.com/javen205/IJPay

关注我们

扫描上面二维码,更多精彩内容每天推荐!

转载声明

如梦技术对此篇文章有最终所有权,转载请注明出处,参考也请注明,谢谢!

广告 广告

评论区