在使用AOP编程的时候,经常碰到需要多次获取整个请求的body的情况。例如:典型场景下我们要在AOP切面中做日志记录或权限校验,此时需要调用request.getInputStream
获取输入流,从而读取整个请求的消息体。但是这通常会触发一个异常:java.lang.IllegalStateException: getInputStream() can't be called after getReader()
。
出现这个问题的原因是默认的HttpServletRequest
对象中的getInputStream
,getReader
函数式只允许调用一次。在一次请求中,除了我们在切面中调用getInputStream
之外,Spring MVC
框架在进行参数转换的时候还需要调用getInputStream
方法读取整个请求的消息体,然后转回为请求参数,这违背了只调用一次的原则,从而触发了以异常,
为了解决这个问题,我们可以引入HttpServletRequestWrapper
这个对象。这个类封装了HttpServletRequest
的行为,我们可以继承这个类,从而使用一个新类模拟原始HttpServletRequest
的行为。然后使用过滤器(filter)将原始的HttpServletRequest
对象替换为HttpServletRequestWrapper
对象。
最近在项目中有需求为API请求增加参数签名校验,使用了AOP切面功能,因此碰到了上面的问题:参数校验切面中需要在读取整个请求报文,然后对报文进行hmac算法从而计算签名值。下面说一下具体的解决办法,以代码为主。
1. 相关代码
1.1 RequestWrapper
RequestWrapper
继承了HttpServletRequestWrapper
,初始化的时候读取Request的整个请求体。然后重载了getInputStream
和getReader
方法,这两个方法会从类变量body
中读取内容。
1public class RequestWrapper extends HttpServletRequestWrapper {
2 private final String body;
3
4 public RequestWrapper(HttpServletRequest request) throws IOException
5 {
6 //So that other request method behave just like before
7 super(request);
8
9 StringBuilder stringBuilder = new StringBuilder();
10 BufferedReader bufferedReader = null;
11 tr