Author:lwyang
SpringBoot:2.1.9.RELEASE
HandlerInterceptor
用于拦截请求进行预处理和后处理,一般用于一下场景:
日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等等。
权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。
性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
HandlerMethodArgumentResolver
是HandlerMethod + Argument(参数) + Resolver(解析器), 其实就是HandlerMethod方法的解析器, 将 HttpServletRequest(header + body 中的内容)解析为HandlerMethod方法的参数,实现该接口可以进行自定义参数解析
ResponseBodyAdvice
是 spring 4.1 新加入的一个接口,在消息体被HttpMessageConverter
写入之前允许Controller 中 @ResponseBody
修饰的方法调整响应中的内容,比如进行相应的加密或者进行统一处理返回值/响应体。【同样RequestBodyAdvice
也是在 sping 新加入的一个接口,它可以使用在 @RequestBody 或 HttpEntity 修饰的参数读取之前进行参数的处理,比如进行参数的解密】
HttpMessageConverter
Http请求响应报文其实都是字符串,当请求报文到java程序会被封装为一个ServletInputStream流,开发人员再读取报文,响应报文则通过ServletOutputStream流,来输出响应报文。从流中只能读取到原始的字符串报文,同样输出流也是,通过@RequestBody、@ResponseBody注解,可以直接将输入解析成Json、将输出解析成Json,那么就存在一个字符串到java对象的转化问题。
前端传入的 json 数据如何被解析成 Java 对象作为 API入参,API 返回结果又如何将 Java 对象解析成 json 格式数据返回给前端,在整个数据流转过程中,HttpMessageConverter
就起到了重要作用。
对@RequestBody
、@ResponseBody
注解进行解析的HandlerMethodArgumentResolver
是RequestResponseBodyMethodProcessor
,而HttpMessageConverter
是通过构造函数注入进来的
public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
上述接口就像是Spring框架提供的Hook函数,可以让我们在请求处理的流程中为一些关键处理点加上我们自己的处理逻辑,满足了不同的需求。
这个思想很类似于在Linux内核里数据包进入IP层处理过程中,会经过Netfilter的五个钩子点,分别是
NF_INET_PRE_ROUTING
、NF_INET_LOCAL_IN
、NF_INET_FORWARD
、NF_INET_LOCAL_OUT
、NF_INET_POST_ROUTING
,在每个点都可以设置一些自定义的规则(插入我们自己的处理函数),来对数据包进行匹配检查处理,这些规则的配置、布局和匹配流程就是钩子函数的作用
测试代码如下:
package com.example.demo.adviceorder;
@RestController
@ControllerAdvice
@RequestMapping("/advice")
public class AdviceOrder implements HandlerInterceptor,
HandlerMethodArgumentResolver, ResponseBodyAdvice {
@GetMapping("/order")
public List adviceOrder(String name, String age, HttpServletRequest request){
System.out.println("======adviceOrder======");
System.out.println("request name:"+request.getParameter("name"));
System.out.println("request age:"+request.getParameter("age"));
System.out.println("name: "+name+" age: "+age);
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add(name);
arrayList.add(age);
return arrayList;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("=======HandlerInterceptor preHandle=======");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("=======HandlerInterceptor postHandle=======");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("=======HandlerInterceptor afterCompletion=======");
}
//-----
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
// System.out.println("======HandlerMethodArgumentResolver supportsParameter====");
return true;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
System.out.println("======HandlerMethodArgumentResolver resolveArgument====");
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return "resolveArgument "+request.getParameter(methodParameter.getParameterName());
}
//------
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
System.out.println("======ResponseBodyAdvice supports====");
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
System.out.println("======ResponseBodyAdvice beforeBodyWrite====");
Map<String, Object>map = new HashMap<>();
map.put("data", body);
Result result = new Result();
result.setCode("200");
result.setMsg("OK");
result.setData(map);
return result;
}
}
我还新增了AOP中的@AfterReturning
,用于比较和ResponseBodyAdvice
的执行顺序:
@org.aspectj.lang.annotation.Aspect
@Component
public class Aspect {
@AfterReturning(value = "execution( * com.example.demo.adviceorder.*.adviceOrder(..))", returning = "string")
public void afterReturning(Object string) {
System.out.println("======AOP afterReturning=======");
}
}
public class Result {
private String code;
private String msg;
private Object data;
//省略get、set方法
}
启动项目在浏览器输入http://localhost:8080/advice/order?name=lwyang&age=10
后,Console打印结果如下:
=======HandlerInterceptor preHandle=======
======HandlerMethodArgumentResolver resolveArgument====
======HandlerMethodArgumentResolver resolveArgument====
======adviceOrder======
request name:lwyang
request age:10
name: resolveArgument lwyang age: resolveArgument 10
======AOP afterReturning=======
======ResponseBodyAdvice supports====
======ResponseBodyAdvice beforeBodyWrite====
=======HandlerInterceptor postHandle=======
=======HandlerInterceptor afterCompletion=======
浏览器返回结果JSON格式:
{
"code": "200",
"msg": "OK",
"data": {
"data": [
"resolveArgument lwyang",
"resolveArgument 10"
]
}
}
根据浏览器返回结果可以看到,浏览器输入的name
为lwyang
,经过ArgumentResolver
解析后变成了resolveArgument lwyang
,同理age
参数,还可以看到返回值在adviceOrder
函数里返回的是一个arrayList
,但经过ResponseBodyAdvice
的处理后,在浏览器返回值中可以看到已经被包装成了一个Result
对象
根据Console打印结果可以看出上述接口在请求中的执行顺序为:
HandlerInterceptor preHandle
==》 HandlerMethodArgumentResolver
》 业务 Method
》AOP afterReturning
== 》ResponseBodyAdvice beforeBodyWrite
》 HttpMessageConverter(转JSON )
>HandlerInterceptor postHandle
==>HandlerInterceptor afterCompletion