文章目录
- 需求场景
- 为什么不用aop?
- 实现方案 : filter + interceptor + request 参数,最干净的原始编程
- 废话少说,搞起:
- 总结
需求场景
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 :
*/
@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的全家桶,所以需要额外配置下)
- 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 也清晰很多。