需求场景
request请求的参数有大概 3种,怎样一次性 都获取出来呢 ?
-
get 请求
?param1=hello¶m2=world
,直接用request.getParameter()
获取单个参数,或者
request.getParameterMap()
获取 多个参数为map格式 -
restful 请求 ,借助 spring实现
(Map)request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
-
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 中 返回
废话少说,搞起:
-
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 :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的全家桶,所以需要额外配置下) -
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(..)
而已 , 不明白的小伙伴可以在下面留言。
-
赠送 一个 重载的方法,判断 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; }
-
总结:步骤 2和3 都是工具类
InterceptorUtils
中 的2个方法,可以在你的项目中 随意使用。比如,在 拦截器 interceptor中,或者 aop 中使用。aop 中,再也不不需要笨重的
pjp.getArgs()
等一系列的复杂操作 来判断参数了 。从此,人们过上了幸福舒心的生活。
总结
我的使用场景是,在拦截器中 ,通过判断 某个特定参数的值,来进行权限控制。
并且,不使用 笨重的 aop ,直接 用 interceptor 完全能胜任。
拦截器中,还可以引入 spring 其他Bean 组件,比如 redissonClient + @Cacheable,实现缓存 某些权限的判断结果,非常好用。过滤器链和 拦截器链,各司其职,整体的代码结构,看起来比 aop 也清晰很多。
推荐1个大神的博客,源码解析 interceptor和aop
全局终,如有疑问,欢迎留言和私信。