在使用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 try {
12 InputStream inputStream = request.getInputStream();
13

在AOP编程中,多次获取HttpServletRequest的body会导致异常,因为默认的HttpServletRequest只允许读取一次。通过继承HttpServletRequestWrapper并创建RequestWrapperFilter,可以在过滤器中将原始HttpServletRequest替换为HttpServletRequestWrapper,从而实现多次读取。RequestWrapper覆盖了getInputStream和getReader方法,使得在ApiAuthAspect切面中可以再次读取请求消息体进行签名校验。

7649

被折叠的 条评论
为什么被折叠?



