关于在拦截器中获取POST请求的参数为空的问题

在写拦截器的时候遇到了需要将请求参数获取并记录的需求,但是发现GET中传递的参数可以直接通过request.getParameter获取,但是Post 传递的参数会获取不到的问题。

1、为啥在拦截器中获取不到POST请求参数但是在Controller中可以获取到?
正常的一个请求流程是这样的:

  1. 客户端发送请求到服务器。
  2. Servlet 容器接收到请求,并创建 HttpServletRequest 对象。
  3. 拦截器执行preHandle() 方法。
  4. 拦截器执行 postHandle() 方法。
  5. 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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值