spring-boot笔记-HandlerInterceptor和MethodInterceptor(AspectJ)

2022-07-22 09:47:33

一、Filter、HandlerInterceptor和MethodInterceptor的区别

在Web开发中,我们经常会用到拦截器。而常用用于实现拦截的有:Filter、HandlerInterceptor、MethodInterceptor。我们也简单了解一下他们的区别:

  1. Filter是Servlet规范规定的,不属于spring框架,也是用于请求的拦截。我们在写Filter时需要自己配置拦截的urlPatterns,它适合更粗粒度的拦截,在请求前后做一些编解码处理、Session验证等。
  2. HandlerInterceptoer拦截的是请求地址,功能能跟Filter类似,但是提供更精细的的控制能力:在request被响应之前、request被响应之后、视图渲染之前以及request全部结束之后。所以针对请求地址做一些验证、预处理等操作比较合适。也可以用作计算一个请求的相应时间等。【必须过DispatcherServlet的请求才会被拦截】
  3. MethodInterceptor利用的是AOP的实现机制,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。

二、HandlerInterceptor的实现

在SpringBoot中实现拦截器也是比较简单的,只需要3步:

  1. 创建我们自己的拦截器类并实现 HandlerInterceptor 接口
  2. 创建一个Java类继承WebMvcConfigurerAdapter,并重写 addInterceptors 方法。
  3. 实例化我们自定义的拦截器,然后将对像手动添加到拦截器链中(在addInterceptors方法中添加)

自定义拦截器代码如下:

import com.example.demo.utils.ScheduleDemo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/**
 * Created by gonghao on 2017/6/3.
 */publicclassGhInterceptorimplementsHandlerInterceptor{privatefinal Logger log = LoggerFactory.getLogger(GhInterceptor.class);@OverridepublicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
        log.info("Controller方法调用前:"+handler.toString());returntrue;
    }@OverridepublicvoidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {
        log.info("Controller方法调用后,视图渲染前:"+handler.toString()+",");
    }@OverridepublicvoidafterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {
        log.info("视图渲染后:"+handler.toString());
    }
}

把拦截器添加到拦截器链中

import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/**
 * Created by gonghao on 2017/6/3.
 */@ConfigurationpublicclassMyWebAppConfigurerextendsWebMvcConfigurerAdapter{@OverridepublicvoidaddInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new GhInterceptor()).addPathPatterns("/**");super.addInterceptors(registry);
    }
}

效果图如下:
这里写图片描述
这里写图片描述
这些方法如我们所想的那样打印了相关的日志,如果大家断点跟进去的话会发现顺序也确实是这么个顺序。这个东西的原理我们可以去看一下DispatcherServlet的doDispatch方法。【方法有点长,这里就不放源码】。

这个doDispatch方法封装了springMVC处理请求的整个过程。首先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器;然后在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。我们可以通过源码分析到: triggerAfterCompletion做的事情就是从当前的拦截器开始逆向调用每个拦截器的afterCompletion方法,并且捕获它的异常,也就是说每个拦截器的afterCompletion方法都会调用。

三、MethodInterceptor(AspectJ)的实现

MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是Controller中的方法。 实现MethodInterceptor拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用Aspect的注解或配置。

关于实现MethodInterceptor接口的这种方法,还需要在配置文件中做配置,在SpringMVC中使用还可以,在SpringBoot中使用起来似乎没有那么方便。

这里我们重点看下Aspect注解方式,这种方法还是相对比较灵活,与配置与工程整个代码都没有耦合【你添加一个类,做几个注解就可以用了,无需在其他地方再做什么】,更易应用。这里我们用一个日志记录的测试类来看下怎么实现。

在pom.xml中添加依赖,需支持AOP才行

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

日志记录测试类

import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.Arrays;/**
 * Created by gonghao on 2017/6/8.
 */@Aspect@ComponentpublicclassLogAspect {privatefinal Logger log = LoggerFactory.getLogger(LogAspect.class);//@Pointcut("@annotation(com.hikvision.energy.core.logaspect.OperationLogAspect)")@Pointcut("execution( * com.example.demo.controller.*.*(..))")publicvoidlogPoint(){

    }@Before("logPoint()")publicvoiddoBefore(JoinPoint joinPoint)throws Throwable {// 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();// 记录下请求内容
        log.info("请求地址 : " + request.getRequestURL().toString());
        log.info("HTTP METHOD : " + request.getMethod());
        log.info("IP : " + request.getRemoteAddr());
        log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() +"."+ joinPoint.getSignature().getName());
        log.info("参数 : " + Arrays.toString(joinPoint.getArgs()));

    }@AfterReturning(returning ="ret", pointcut ="logPoint()")// returning的值和doAfterReturning的参数名一致publicvoiddoAfterReturning(Object ret)throws Throwable {// 处理完请求,返回内容
        log.info("返回值 : " + ret);
    }@Around("logPoint()")public ObjectdoAround(ProceedingJoinPoint pjp)throws Throwable {long startTime = System.currentTimeMillis();
        Object ob = pjp.proceed();// ob 为方法的返回值
        log.info("耗时 : " + (System.currentTimeMillis() - startTime));return ob;
    }

}

来看下效果:
请求还是上面那个请求,我们正好也可以看看这两个拦截器的执行先后顺序。下面是控制台输出,可以看到,是先执行HandlerInterceptor的pre方法,在执行AspectJ的方法级别拦截,最后在执行HandlerInterceptor的其他方法。

2017-06-0911:32:21.154 [http-nio-8080-exec-1] INFOcom.example.demo.utils.interceptor.GhInterceptor[GhInterceptor.java:21] - Controller方法调用前:public java.util.Map<java.lang.String, java.lang.Object>com.example.demo.controller.SysInfoController.testProp()2017-06-0911:32:21.169 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:37] - 请求地址 : http://localhost:8080/sysInfo/testProp2017-06-0911:32:21.169 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:38] - HTTP METHOD : GET2017-06-0911:32:21.170 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:39] - IP :0:0:0:0:0:0:0:12017-06-0911:32:21.172 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:40] - CLASS_METHOD :com.example.demo.controller.SysInfoController.testProp2017-06-0911:32:21.173 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:41] - 参数 : []2017-06-0911:32:21.182 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:55] - 耗时 :142017-06-0911:32:21.183 [http-nio-8080-exec-1] INFOcom.example.demo.utils.aop.LogAspect[LogAspect.java:48] - 返回值 : {randomInt=56867, randomValue=36053cc2ad86fda2aa60a8f3dc849d77, name=www.loveaocai.com, title=hello world, desc=www.loveaocai.com is a domain name}2017-06-0911:32:21.253 [http-nio-8080-exec-1] INFOcom.example.demo.utils.interceptor.GhInterceptor[GhInterceptor.java:27] - Controller方法调用后,视图渲染前:public java.util.Map<java.lang.String, java.lang.Object>com.example.demo.controller.SysInfoController.testProp(),2017-06-0911:32:21.254 [http-nio-8080-exec-1] INFOcom.example.demo.utils.interceptor.GhInterceptor[GhInterceptor.java:32] - 视图渲染后:public java.util.Map<java.lang.String, java.lang.Object>com.example.demo.controller.SysInfoController.testProp()

注意:

  1. 在application.properties中也不需要添加spring.aop.auto=true,因为这个默认就是true,值为true就是启用@EnableAspectJAutoProxy注解了。你不需要手工添加 @EnableAspectJAutoProxy 注解。
  2. 当你需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,这个默认值是false,不然默认使用的是标准Java的实现。

其实aspectj的拦截器会被解析成AOP中的advice,最终被适配成MethodInterceptor,这些都是Spring自动完成的,如果你有兴趣,详细的过程请参考springAOP的实现。

参考:
1.谈谈spring中的拦截器interceptor
2. SpringBoot AOP 拦截器 Aspect

  • 作者:I,Frankenstein
  • 原文链接:https://blog.csdn.net/u013185616/article/details/72926966
    更新时间:2022-07-22 09:47:33