SpringBoot 实战,自定义 Filter 优雅获取请求参数和响应结果

有一定经验的都知道,如果我们在Filter中读取了HttpServletRequest或者HttpServletResponse的流,就没有办法再次读取了,这样就会造成请求异常。所以,我们需要借助 Spring 提供的ContentCachingRequestWrapperContentCachingRequestWrapper实现数据流的重复读取。

定义 Filter


通常来说,我们自定义的Filter是实现Filter接口,然后写一些逻辑,但是既然是在 Spring 中,那就借助 Spring 的一些特性。在我们的实现中,要继承OncePerRequestFilter实现我们的自定义实现。

从类名上推断,OncePerRequestFilter是每次请求只执行一次,但是,难道Filter在一次请求中还会执行多次吗?Spring 官方也是给出定义这个类的原因:

Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. It provides a doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) method with HttpServletRequest and HttpServletResponse arguments.

As of Servlet 3.0, a filter may be invoked as part of a REQUEST or ASYNC dispatches that occur in separate threads. A filter can be configured in web.xml whether it should be involved in async dispatches. However, in some cases servlet containers assume different default configuration. Therefore sub-classes can override the method shouldNotFilterAsyncDispatch() to declare statically if they should indeed be invoked, once, during both types of dispatches in order to provide thread initialization, logging, security, and so on. This mechanism complements and does not replace the need to configure a filter in web.xml with dispatcher types.

Subclasses may use isAsyncDispatch(HttpServletRequest) to determine when a filter is invoked as part of an async dispatch, and use isAsyncStarted(HttpServletRequest) to determine when the request has been placed in async mode and therefore the current dispatch won’t be the last one for the given request.

Yet another dispatch type that also occurs in its own thread is ERROR. Subclasses can override shouldNotFilterErrorDispatch() if they wish to declare statically if they should be invoked once during error dispatches.

也就是说,Spring 是为了兼容不同的 Web 容器,所以定义了只会执行一次的OncePerRequestFilter

接下来开始定义我们的Filter类:

public class AccessLogFilter extends OncePerRequestFilter {

//… 这里有一些必要的属性

@Override

protected void doFilterInternal(final HttpServletRequest request,

final HttpServletResponse response,

final FilterChain filterChain)

throws ServletException, IOException {

// 如果是被排除的 uri,不记录 access_log

if (matchExclude(request.getRequestURI())) {

filterChain.doFilter(request, response);

return;

}

final String requestMethod = request.getMethod();

final boolean shouldWrapMethod = StringUtils.equalsIgnoreCase(requestMethod, HttpMethod.PUT.name())

|| StringUtils.equalsIgnoreCase(requestMethod, HttpMethod.POST.name());

final boolean isFirstRequest = !isAsyncDispatch(request);

final boolean shouldWrapRequest = isFirstRequest && !(request instanceof ContentCachingRequestWrapper) && shouldWrapMethod;

final HttpServletRequest requestToUse = shouldWrapRequest ? new ContentCachingRequestWrapper(request) : request;

final boolean shouldWrapResponse = !(response instanceof ContentCachingResponseWrapper) && shouldWrapMethod;

final HttpServletResponse responseToUse = shouldWrapResponse ? new ContentCachingResponseWrapper(response) : response;

final long startTime = System.currentTimeMillis();

Throwable t = null;

try {

filterChain.doFilter(requestToUse, responseToUse);

} catch (Exception e) {

t = e;

throw e;

} finally {

doSaveAccessLog(requestToUse, responseToUse, System.currentTimeMillis() - startTime, t);

}

}

// … 这里是一些必要的方法

这段代码就是整个逻辑的核心所在,其他的内容从源码中找到。

分析

这个代码中,整体的逻辑没有特别复杂的地方,只需要注意几个关键点就可以了。

  1. 默认的HttpServletRequestHttpServletResponse中的流被读取一次之后,再次读取会失败,所以要使用ContentCachingRequestWrapperContentCachingResponseWrapper进行包装,实现重复读取。

  2. 既然我们可以自定义Filter,那我们依赖的组件中也可能会自定义Filter,更有可能已经对请求和响应对象进行过封装,所以,一定要先进行一步判断。也就是request instanceof ContentCachingRequestWrapperresponse instanceof ContentCachingResponseWrapper

只要注意了这两点,剩下的都是这个逻辑的细化实现。

运行

接下来我们就运行一遍,看看结果。先定义几种不同的请求:普通 get 请求、普通 post 请求、上传文件、下载文件,这四个接口几乎可以覆盖绝大部分场景。(因为都是比较简单的写法,源码就不赘述了,可以从文末的源码中找到)

先启动项目,然后借助 IDEA 的 http 请求工具:

###普通 get 请求

GET http://localhost:8080/index/get?name=howard

###普通 post 请求

POST http://localhost:8080/index/post

结语

小编也是很有感触,如果一直都是在中小公司,没有接触过大型的互联网架构设计的话,只靠自己看书去提升可能一辈子都很难达到高级架构师的技术和认知高度。向厉害的人去学习是最有效减少时间摸索、精力浪费的方式。

我们选择的这个行业就一直要持续的学习,又很吃青春饭。

虽然大家可能经常见到说程序员年薪几十万,但这样的人毕竟不是大部份,要么是有名校光环,要么是在阿里华为这样的大企业。年龄一大,更有可能被裁。

送给每一位想学习Java小伙伴,用来提升自己。

在这里插入图片描述

本文到这里就结束了,喜欢的朋友可以帮忙点赞和评论一下,感谢支持!
习,又很吃青春饭。

虽然大家可能经常见到说程序员年薪几十万,但这样的人毕竟不是大部份,要么是有名校光环,要么是在阿里华为这样的大企业。年龄一大,更有可能被裁。

送给每一位想学习Java小伙伴,用来提升自己。

[外链图片转存中…(img-gIu8PCnb-1721155227706)]

本文到这里就结束了,喜欢的朋友可以帮忙点赞和评论一下,感谢支持!

Spring Boot自定义 Filter 处理根据不同请求参数返回的情况通常是在需要对特定请求请求参数做预处理、修改或者增强的时候。例如,你可以创建一个 Filter 来: 1. **添加全局日志记录**:如果每个请求都需要记录某些信息,可以根据请求参数的不同来调整日志内容。 2. **身份验证和授权**:基于请求头或参数判断用户是否有权限访问资源。 3. **API版本控制**:根据请求参数(如版本号)动态选择不同的 API 实现。 4. **数据转换**:对于特定的请求参数,先解析成业务对象,然后进一步处理。 以下是一个简单的自定义 Filter 的例子: ```java @Component public class MyFilter implements WebFilter { @Override public Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) throws IOException { ServerHttpRequest request = exchange.getRequest(); String parameterToCheck = request.getQueryParams().getFirst("parameterKey"); // 获取请求参数 if (checkParameter(parameterToCheck)) { // 根据条件检查参数 // 对响应进行处理 return chain.filter(exchange).flatMap(response -> { response.getHeaders().add("custom-header", "filtered-response"); return response; }); } else { // 如果不符合条件,直接传递到下一个过滤器 return chain.filter(exchange); } } private boolean checkParameter(String parameterValue) { // 根据实际需求编写检查逻辑 // ... } // 其他可能需要的方法,如链式初始化等 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值