不用aop,获取 request 对象中所有形式的参数,包含 get参数,post body参数,restful格式的所有参数。put all parameters into 1 single map

文章目录

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

需求场景

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

  1. get 请求 ?param1=hello¶m2=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
     * 

    * reference :https://blog.csdn.net/dark_horse_lk/article/details/82344692 */ @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); // https://www.cnblogs.com/keeya/p/13634015.html 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的全家桶,所以需要额外配置下)

  2. 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 格式 .所以用 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(..) 而已 ,

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

  3. 赠送 一个 重载的方法,判断 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;
    }
    
  4. 总结:步骤 2和3 都是工具类 InterceptorUtils 中 的2个方法,可以在你的项目中 随意使用。比如,在 拦截器 interceptor中,或者 aop 中使用。

    aop 中,再也不不需要笨重的 pjp.getArgs() 等一系列的复杂操作 来判断参数了 。

    从此,人们过上了幸福舒心的生活。

总结

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

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

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

推荐1个大神的博客,源码解析 interceptor和aop

全局终,如有疑问,欢迎留言和私信。

你可能感兴趣的:(spring,web,java,restful,java,后端)