Spring解密之HandlerInterceptor、HandlerMethodArgumentResolver、ResponseBodyAdvice执行顺序

Author:lwyang
SpringBoot:2.1.9.RELEASE

概述

HandlerInterceptor用于拦截请求进行预处理和后处理,一般用于一下场景:

  1. 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等等。

  2. 权限检查:如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面。

  3. 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);

  4. 通用行为:读取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注解进行解析的HandlerMethodArgumentResolverRequestResponseBodyMethodProcessor,而HttpMessageConverter是通过构造函数注入进来的

public RequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
		super(converters);
	}

上述接口就像是Spring框架提供的Hook函数,可以让我们在请求处理的流程中为一些关键处理点加上我们自己的处理逻辑,满足了不同的需求。

这个思想很类似于在Linux内核里数据包进入IP层处理过程中,会经过Netfilter的五个钩子点,分别是
NF_INET_PRE_ROUTINGNF_INET_LOCAL_INNF_INET_FORWARDNF_INET_LOCAL_OUTNF_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"
		]
	}
}

根据浏览器返回结果可以看到,浏览器输入的namelwyang,经过ArgumentResolver解析后变成了resolveArgument lwyang,同理age参数,还可以看到返回值在adviceOrder函数里返回的是一个arrayList,但经过ResponseBodyAdvice的处理后,在浏览器返回值中可以看到已经被包装成了一个Result对象

根据Console打印结果可以看出上述接口在请求中的执行顺序为:
HandlerInterceptor preHandle ==》 HandlerMethodArgumentResolver 》 业务 MethodAOP afterReturning == 》ResponseBodyAdvice beforeBodyWrite HttpMessageConverter(转JSON )>HandlerInterceptor postHandle ==>HandlerInterceptor afterCompletion

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC的HandlerInterceptor和ExceptionHandler是两个不同的拦截器,它们的执行机制如下: 1. HandlerInterceptor的执行机制 在Spring MVC中,HandlerInterceptor是一个拦截器接口,用于在Controller方法执行前、执行后和视图渲染后进行处理。HandlerInterceptor执行顺序是在DispatcherServlet中进行管理的,可以通过注册Interceptor来实现。 当请求到达DispatcherServlet时,DispatcherServlet会遍历所有注册的Interceptors,并按照注册的顺序依次执行它们的preHandle方法。如果preHandle方法返回true,则表示继续执行后续的拦截器和Controller方法;如果preHandle方法返回false,则表示请求处理结束,后续的拦截器和Controller方法将不会执行。 在Controller方法执行完毕之后,DispatcherServlet会再次遍历所有注册的Interceptors,并按照相反的顺序依次执行它们的postHandle方法。最后,在视图渲染完成之后,DispatcherServlet会再次遍历所有注册的Interceptors,并按照相反的顺序依次执行它们的afterCompletion方法。 2. ExceptionHandler的执行机制 ExceptionHandlerSpring MVC中的异常处理器,用于处理Controller方法中抛出的异常。ExceptionHandler可以通过在Controller类或方法上添加@ExceptionHandler注解来声明,当Controller方法中抛出指定类型的异常时,ExceptionHandler会自动捕获该异常并进行处理。 ExceptionHandler执行顺序是在DispatcherServlet中进行管理的,当Controller方法中抛出指定类型的异常时,DispatcherServlet会遍历所有注册的ExceptionHandler,并寻找可以处理该异常的ExceptionHandler。如果找到了可以处理该异常的ExceptionHandler,则执行它的处理方法;如果没有找到,则将异常抛出到Servlet容器中进行处理。 综上所述,HandlerInterceptor和ExceptionHandler都是Spring MVC中的拦截器,它们的执行机制不同。HandlerInterceptor在Controller方法执行前、执行后和视图渲染后进行处理,ExceptionHandler则是用于处理Controller方法中抛出的异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值