在写拦截器的时候遇到了需要将请求参数获取并记录的需求,但是发现GET中传递的参数可以直接通过request.getParameter获取,但是Post 传递的参数会获取不到的问题。
1、为啥在拦截器中获取不到POST请求参数但是在Controller中可以获取到?
正常的一个请求流程是这样的:
- 客户端发送请求到服务器。
- Servlet 容器接收到请求,并创建 HttpServletRequest 对象。
- 拦截器执行preHandle() 方法。
- 拦截器执行 postHandle() 方法。
- Controller 执行。
在第2步中,Servlet容器创建 HttpServletRequest 对象时,只会初始化 HttpServletRequest 对象的属性,而不会初始化 HttpServletRequest 对象的输入流。因此,在拦截器中使用 request.getParameterMap() 或 request.getReader() 方法获取参数时,会获取不到参数。
而Controller 可以获取到请求参数是因为,在 Controller 执行之前,Servlet容器会调用 HttpServletRequest.getInputStream() 方法获取请求的输入流,并将输入流传递给 Controller。Controller 可以使用 InputStream 对象来读取请求参数。
2、为啥不创建HttpServletRequest 对象的时候就把输入流也初始化呢?
Servlet容器不选择在初始化 HttpServletRequest 对象的时候就把输入流也初始化的原因有以下几点:
提高效率:如果在初始化 HttpServletRequest 对象的时候就把输入流也初始化,则需要在 HttpServletRequest 对象的构造函数中将输入流从 HTTP 连接中读取出来。这会导致 HttpServletRequest 对象的构造函数更加复杂,而且会影响 HttpServletRequest 对象的创建效率。
降低耦合度:如果在初始化 HttpServletRequest 对象的时候就把输入流也初始化,则会增加 HttpServletRequest 对象和 HTTP 连接之间的耦合度。这样会导致 HttpServletRequest 对象的使用更加复杂,而且会增加 HttpServletRequest 对象的维护难度。
提高灵活性:如果在初始化 HttpServletRequest 对象的时候不把输入流也初始化,则可以将输入流的初始化延迟到需要使用输入流的时候。这样可以提高灵活性,因为可以根据实际情况来决定是否需要初始化输入流。
根据这些原因,Servlet 规范中规定,Servlet容器在初始化 HttpServletRequest 对象的时候不应该初始化输入流。这样可以提高效率、降低耦合度、提高灵活性。
所以解决办法就是,把输入流保存下来,先给拦截器使用,然后再给Controller使用
至于为什么要保存下来?因为request.getInputStream()只能用一次,原因是 request.getInputStream() 方法返回的 ServletInputStream 对象不支持 reset() 方法。reset() 方法用于将输入流重置到初始位置,这样就可以再次读取输入流。而ServletInputStream 对象是 InputStream 的子类,但是它没有重写 reset() 方法。这是因为 ServletInputStream 对象是专门用于处理 HTTP 请求的,而 HTTP 请求是一次性的。因此,为了保证 HTTP 请求的完整性,ServletInputStream 对象不允许重复读取。
解决办法:
新建一个自定义的 HttpServletRequestWrapper 类来获取请求参数:
/**
* 自定义一个HttpServletRequestWrapper
* 将输入流保存下来,这样就能多次读取输入的参数了
*/
public class MyRequestWrapper extends HttpServletRequestWrapper {
private String body;
public RequestWrapper(HttpServletRequest request) throws IOException{
super(request);
body = getRequestBody(request);
}
private String getRequestBody(HttpServletRequest request) throws IOException {
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){
inputStream.close();
}
if (reader != null){
reader.close();
}
}
return sb.toString();
}
/**
* 重写getInputStream,这样Controller或其他地方就能多次读取输入的参数了(返回保存下来的body)
*/
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes("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 byteArrayInputStream.read();
}
};
}
/**
* 重写获取 字符流的方式
*/
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream(), Charsets.UTF_8));
}
public String getBody() {
return this.body;
}
}
自定义一个DispatcherServlet子类来分派自定义的HttpServletRequest:
public class MyDispatcherServlet extends DispatcherServlet {
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
super.doDispatch(new MyRequestWrapper(request), response);
}
}
配置自定义的DispatcherServlet
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
// ......其他配置
@Bean
@Qualifier(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet() {
return new MyDispatcherServlet();
}
}
在拦截器中使用:
if (httpServletRequest.getMethod().equalsIgnoreCase("GET")) {
userAccessRecords.setParams(httpServletRequest.getQueryString());
} else if (httpServletRequest.getMethod().equalsIgnoreCase("POST")) {
// 如果是POST请求,获取请求体参数
MyRequestWrapper myRequestWrapper = new MyRequestWrapper(httpServletRequest);
userAccessRecords.setParams(myRequestWrapper.getBody());
}
参考:https://www.cnblogs.com/keeya/p/13634015.html