文章目录

  • 需求场景
  • 为什么不用aop?
  • 实现方案 : filter + interceptor + request 参数,最干净的原始编程
  • 废话少说,搞起:
  • 总结


需求场景

request请求的参数有大概 3种,怎样一次性 都获取出来呢 ?

  1. get 请求 ?param1=hello&param2=world ,直接用request.getParameter()获取单个参数,
    或者 request.getParameterMap() 获取 多个参数为map格式
  2. restful 请求 ,借助 spring实现 (Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
  3. post 请求的参数,读取 stream 流 ,request.getInputStream() 获取 。 该方法只允许调用1次,因为 stream的流指针没有复位,导致后续执行会报错。因此,我们需要 提前 封装一下它的内容 到 buffer 中,曲线救国实现 多次读取 。
为什么不用aop?
  • 原理:
  • 拦截器的实现原理是 HandlerMapping 根据request的url和注解信息等,找到拦截器调用链中的每个Interceptor,然后传递 request 进去依次执行。不涉及 动态代理
  • 对比而言,aop是动态代理,每次都需要生成1个实现类,就问 cpu和内存 累不累,code monkey 累不累
  • 代码太啰嗦,用的人太多 烂大街了。
  • 大量重复的代码,一点也不高雅。
  • 需要换个口味,回归自然,回归简单,如 诺基亚C20L plus 一般。
实现方案 : filter + interceptor + request 参数,最干净的原始编程
  • filter 封装request对象,使其 内部的 stream 流可以 多次 读
  • interceptor 拦截每次 指定的 请求, 获取 request 对象中的 所有parameters ,并且最终 放入 1 个map 中 返回
废话少说,搞起:
  1. WrapRequestFilter 过滤器,封装request 对象,替换 原有的request,支持 多次 执行 request.getInputStream()
/**
 * enable the ability to read request-params multiple times
 * by copying the request stream into a byte[] buffer.
 * getInputStream() & getReader() are both override to read this buffer
 *
 * @author stormfeng
 * @date 2021-08-12  10:30
 * <p>
 * reference :
 */
@WebFilter(urlPatterns = "/api/*")
@Order(2)
public class WrapRequestFilter extends OncePerRequestFilter {
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
     ServletRequest requestWrapper = new BufferedServletRequestWrapper(request);
     chain.doFilter(requestWrapper, response);
 }

 private static class BufferedServletRequestWrapper extends HttpServletRequestWrapper {
     private byte[] buffer;

     public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
         super(request);
         InputStream is = request.getInputStream();
         this.buffer = StreamUtils.copyToByteArray(is);
     }

     // 外部 读取字节流 的方法
     @Override
     public ServletInputStream getInputStream() throws IOException {
         InputStream bodyStream = new ByteArrayInputStream(this.buffer);
         // 
         return new ServletInputStream() {
             @Override
             public int read() throws IOException {
                 return bodyStream.read();
             }

             @Override
             public boolean isFinished() {
                 return false;
             }

             @Override
             public boolean isReady() {
                 return false;
             }

             @Override
             public void setReadListener(ReadListener listener) {
             }
         };
     }

     // 外部 读取字符流 的方法
     @Override
     public BufferedReader getReader() throws IOException {
         return new BufferedReader(new InputStreamReader(getInputStream()));
     }
 }
}

注意: 在 springboot 中, @WebFilter 这个注解要想生效,需要在 boot的启动 main类上 加上 @ServletComponentScan ,开启扫描servlet容器组件的功能。(本身filter不是spring的全家桶,所以需要额外配置下)

  1. InterceptorUtils 自定义的工具类,获取 所有参数
/**
 * @author stormfeng
 * @date 2021-08-12  19:19
 */
@Slf4j
public class InterceptorUtils {

    /**
     * 获取所有参数,集成到 1个map 中展示给前端
     */
    public static Map getParamMap(HttpServletRequest request) throws IOException {
        Map<String, Object> resultMap = new HashMap<>();
        // get 请求
        Map<String, String[]> parameterMap = request.getParameterMap();
        resultMap.putAll(parameterMap);

        // restful请求
        Object attribute = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        if (attribute instanceof Map) {
            resultMap.putAll((Map) attribute);
        }

        // post body 参数
        Object postBody = getPostBody(request);
        if (null != postBody) {
            resultMap.put("posytBody", postBody);
        }

        // 因为请求可能是多个key,value为数组,导致直接 toString 会 很丑,
        // 所以需要 jackson 提前序列化一下values ,不至于太丑看不懂 [java... 到底是什么 
        for (Map.Entry<String, Object> entry : resultMap.entrySet()) {
            entry.setValue(JacksonUtils.writeValueAsString(entry.getValue()));
        }
        return resultMap;
    }
     public static Object getPostParam(HttpServletRequest request, String requiredParam) throws IOException {
         Object postBody = getPostBody(request);
 
         if (null == postBody) {
             return null;
         }
         if (postBody instanceof Map) {
             return ((Map) postBody).get(requiredParam);
         } else if (postBody instanceof List) {
             for (Map<String, Object> map : (List<Map<String, Object>>) postBody) {
                 if (map.get(requiredParam) != null) {
                     return map.get(requiredParam);
                 }
             }
         }
 
         return null;
     }

    /**
	 * @return 有可能 返回 Map 格式,也可能返回 List<Map> 格式 .所以用 Object 接参
     */
    public static Object getPostBody(HttpServletRequest request) throws IOException {
        int contentLength = request.getContentLength();
        if (contentLength <= 0) {
            return null;
        }

        String charEncoding = request.getCharacterEncoding();
        if (charEncoding == null) {
            charEncoding = "UTF-8";
        }

        String body = new String(StreamUtils.copyToByteArray(request.getInputStream()), charEncoding);
        return JacksonUtils.readValue(body, Object.class);
    }
}

注意: 上面代码,最后一步中的 JacksonUtils 是一个极其简单的封装, 其实就是 单例的

new ObjectMapper().writeValueAsString(..)new ObjectMapper().readValue(..) 而已 ,

不明白的小伙伴可以在下面留言。

  1. 赠送 一个 重载的方法,判断 request中 是否包含 某个 参数 requiredParam
/**
 * try to get a requiredParam by 3 ways below:
 * 1)query string,like ?name=hello&id=1
 * 2)uri pattern params,like /{name}/1 --> /myname/1
 * 3)if post ,check post body
 * @return null,则代表不包含 参数的 requiredParam ,或者该参数的值 value 不存在 
 */
public static Object getParam(HttpServletRequest request, String requiredParam) throws IOException {
    if (null != request.getParameter(requiredParam)) {
        return request.getParameter(requiredParam);
    }

    Object uriParamMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
    if (uriParamMap instanceof Map) {
        Object o = ((Map) uriParamMap).get(requiredParam);
        if (null != o) {
            return o;
        }
    }

    if (HttpMethod.POST.matches(request.getMethod())) {
        return getPostParam(request,requiredParam);
    }

    return null;
}
  1. 总结:步骤 2和3 都是工具类 InterceptorUtils 中 的2个方法,可以在你的项目中 随意使用。比如,在 拦截器 interceptor中,或者 aop 中使用。
    aop 中,再也不不需要笨重的 pjp.getArgs() 等一系列的复杂操作 来判断参数了 。
    从此,人们过上了幸福舒心的生活。
总结

我的使用场景是,在拦截器中 ,通过判断 某个特定参数的值,来进行权限控制。

并且,不使用 笨重的 aop ,直接 用 interceptor 完全能胜任。

拦截器中,还可以引入 spring 其他Bean 组件,比如 redissonClient + @Cacheable,实现缓存 某些权限的判断结果,非常好用。过滤器链和 拦截器链,各司其职,整体的代码结构,看起来比 aop 也清晰很多。