大聪明教你学Java | SpringBoot 项目里如何在拦截器中获取 @RequestBody 参数
前言
“我正在参加「掘金·启航计划」”
🍊作者简介: 不肯过江东丶,一个来自二线城市的程序员,致力于用“猥琐”办法解决繁琐问题,让复杂的问题变得通俗易懂。 🍊支持作者: 点赞👍、关注💖、留言💌~
最近项目中来了一个新的需求:需要搞一个拦截器,对请求接口时传递过来的参数做一个加密处理。这个需求本身并不难,但是在实现功能的过程中却遇到了一个比较“棘手”的问题...
踩坑之路
问题描述
在拦截器中获取接口参数的时候需要分两种情况:
🍓情况一:接口使用 @RequestParam 接收参数 🍓情况二:接口使用 @RequestBody 接收参数
针对情况一,代码写起来就非常简单了,我们只需要在拦截器中通过request.getParameterMap() 来获得全部 Parameter 参数就可以了;但是当接口使用 @RequestBody 接收参数时,我们在拦截器中使用同样的方法获取参数,就会出现流已关闭的异常,也就导致参数读取失败了😔... 这是因为 Spring 已经对 @RequestBody 提前进行处理,而 HttpServletReqeust 获取输入流时仅允许读取一次,所以会报java.io.IOException: Stream closed。
俗话说“繁琐问题必有猥琐解法”,既然 HttpServletReqeust 获取输入流时仅允许读取一次,那么我们就可以重新构建 ServletRequest ,让输入流支持二次读取就可以了 ( •̀ ω •́ )y
解决方案
既然解决思路已经有了,那么我们就可以直接写代码了,那么这里就不再多说,直接上代码 👇
🍊 ① 定义过滤器,所有请求先进入过滤器,并将 request 进行处理
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/***
* HttpServletRequest 过滤器
* 解决: request.getInputStream()只能读取一次的问题
* 目标: 流可重复读
*
* @author liziye
*/
@Component
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/")
@Order(99)
public class HttpServletRequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
//获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新 request对象中
// 在chain.doFiler方法中传递新的request对象
if(null == requestWrapper) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
/***
* HttpServletRequest 包装器
* 解决: request.getInputStream()只能读取一次的问题
* 目标: 流可重复读
*/
public class RequestWrapper extends HttpServletRequestWrapper {
/**
* 请求体
*/
private String mBody;
public RequestWrapper(HttpServletRequest request) {
super(request);
// 将body数据存储起来
mBody = getBody(request);
}
/**
* 获取请求体
* @param request 请求
* @return 请求体
*/
private String getBody(HttpServletRequest request) {
return HttpContextUtils.getBodyString(request);
}
/**
* 获取请求体
* @return 请求体
*/
public String getBody() {
return mBody;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 创建字节数组输入流
final ByteArrayInputStream bais = new ByteArrayInputStream(mBody.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
}
}
🍊 ② 工具类,获取请求的 body 体
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* HttpContextUtils
*
* @author liziye
*/
public class HttpContextUtils {
/**
* 获取query参数
* @param request
* @return
*/
public static Map<String, String> getParameterMapAll(HttpServletRequest request) {
Enumeration<String> parameters = request.getParameterNames();
Map<String, String> params = new HashMap<>();
while (parameters.hasMoreElements()) {
String parameter = parameters.nextElement();
String value = request.getParameter(parameter);
params.put(parameter, value);
}
return params;
}
/**
* 获取请求Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
🍊 ③ 拦截器中的具体使用
首先判断本次 request 请求对象是不是 RequestWrapper 类的实例,如果是,则代表本次请求的接口是使用 @RequestBody 来接收的参数,那么我们就需要用 getBodyString() 方法获取参数;反之,则代表接口使用 @RequestParam 接收参数,我们直接用 request.getParameterMap() 来获得全部参数即可。
小结
本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇
希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)
如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。
爱你所爱 行你所行 听从你心 无问东西
转载自:https://juejin.cn/post/7202799873841070136