SpringBoot过滤器、拦截器、切面之间的区别和执行顺序

SpringBoot过滤器、拦截器、切面之间的区别和执行顺序

相关原理

首先了解一下SpringMVC的执行流程

具体流程如下:

  • 用户发起请求到前端控制器(Controller)

  • 前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。

  • HandlerMapping返回执行链,包含了2部分内容:① Handler对象、② 拦截器数组

  • 前端处理器通过处理器适配器包装后执行Handler对象。

  • 处理业务逻辑。

  • Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。

  • 将ModelAndView返回给前端控制器。

  • 视图解析器(ViewResolver)返回真正的视图对象(View)。

  • (此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。

  • 返回渲染后的视图(html/json/xml)返回。

  • 给用户产生响应。

核心就是DispatcherServlet核心控制器,我们看源码可知道DispatcherServlet是Servlet的子类

而过滤器、Servlet容器、拦截器、AOP、Controller之间的关系

然后具体执行流程如下:

拦截器和过滤器的区别

  • 拦截器不依赖与servlet容器是SpringMVC自带的,过滤器依赖于Servlet容器。

  • 拦截器是基于java的反射机制的,而过滤器是基于函数回调。

  • 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。

  • 拦截器可以访问controller上下文、值栈里的对象,而过滤器不能访问。

    拦截器的preHandle方法在进入controller前执行,而拦截器的postHandle方法在执行完controller业务流程后,在视图解析器解析ModelAndView之前执行,可以操控Controller的ModelAndView内容。而afterCompletion是在视图解析器解析渲染ModelAndView完成之后执行的

    过滤器是在服务器启动时就会创建的,只会创建一个实例,常驻内存,也就是说服务器一启动就会执行Filter的init(FilterConfig config)方法.当Filter被移除或服务器正常关闭时,会执行destroy方法

  • 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

    关于这句话的解读是:我们知道拦截器是SprinMVC自带的,而SpringMVC存在Controller层的,而controller层可以访问到service层,service层是不能访问service层的,而过滤器是客户端和服务端之间请求与响应的过滤

  • 过滤器和拦截器触发时机、时间、地方不一样

    过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是在servlet处理完后,返回给前端

  • 过滤器包裹住servlet,servlet包裹住拦截器。

代码实现

目录结构

 |-aspect
 |---------TestAspect // 切面
 |-config
 |---------InterceptorConfig // 拦截器配置
 |-filter
 |---------TestFilter1 // 过滤器
 |---------TestFilter2
 |-interceptor
 |---------TestInterceptor1 // 拦截器
 |---------TestInterceptor2
 |-rest
 |---------TestController
 |-MySpringApplication

POM依赖

     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <version>2.6.0</version>
         </dependency>
         <!-- AOP -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
             <version>2.6.0</version>
         </dependency>
         <!-- fastjson -->
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.80</version>
         </dependency>
         <!-- lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <version>1.18.20</version>
         </dependency>
     </dependencies>
View Code

MySpringApplication

 @SpringBootApplication
 @ServletComponentScan
 public class MySpringApplication {
     public static void main(String[] args) {
         SpringApplication.run(MySpringApplication.class, args);
     }
 }
View Code

TestController

 @RequestMapping("/test")
 @RestController
 @Slf4j
 public class TestController {
     @GetMapping(value = "/find")
     public String findInterfaceList(@RequestParam String userName) {
         log.info("查询数据成功!!!!");
         return userName;
     }
 }
View Code

过滤器

 /**
  * @author MMDZ
  * @description TODO 过滤器执行顺序会按照filterName的字母顺序进行
  * @WebFilter 指定的过滤器优先级都高于FilterRegistrationBean配置的过滤器
  * <p>
  * 注:这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。
  * 这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,
  * 我们需在启动类中加注解:@ServletComponetScan,指定扫描的包。
  */
 @Slf4j
 @WebFilter(urlPatterns = "/*", filterName = "aaaa")
 public class TestFilter1 implements Filter {
     @Override
     public void init(FilterConfig filterConfig) {
         log.info("过滤器1初始化!!!!");
     }
 ​
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         log.info("过滤器1开始!!!!");
         long start = System.currentTimeMillis();
         filterChain.doFilter(servletRequest, servletResponse);
         log.info("过滤器1结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 ​
     @Override
     public void destroy() {
         //当Filter被移除或服务器正常关闭时
         log.info("过滤器1销毁!!!!");
     }
 }
 ​
 // -------------------------------------------------------------------------
 @Slf4j
 @WebFilter(urlPatterns = "/*", filterName = "bbbb")
 public class TestFilter2 implements Filter {
     @Override
     public void init(FilterConfig filterConfig) {
         log.info("过滤器2初始化!!!!");
     }
 ​
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         log.info("过滤器2开始!!!!");
         long start = System.currentTimeMillis();
         filterChain.doFilter(servletRequest, servletResponse);
         log.info("过滤器2结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 ​
     @Override
     public void destroy() {
         //当Filter被移除或服务器正常关闭时
         log.info("过滤器2销毁!!!!");
     }
 }
View Code

拦截器&配置

 /**
  * @author MMDZ
  * @description TODO 拦截器配置: 实现WebMvcConfigurer接口并且重写addInterceptors方法
  */
 @Configuration
 public class InterceptorConfig implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         InterceptorRegistration interceptorRegistration1 = registry.addInterceptor(new TestInterceptor1());
         interceptorRegistration1.addPathPatterns("/**");
         interceptorRegistration1.order(1);
 ​
         InterceptorRegistration interceptorRegistration2 = registry.addInterceptor(new TestInterceptor2());
         interceptorRegistration2.addPathPatterns("/**");
         interceptorRegistration2.order(2);
     }
 }
 ​
 // -------------------------------------------------------------------------
 @Slf4j
 public class TestInterceptor1 implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         log.info("请求执行前拦截器11111开始!!!!");
         long start = System.currentTimeMillis();
         log.info("请求执行前拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
         return true;//当返回true的时候,才可以执行postHandle/afterCompletion
     }
 ​
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         log.info("请求执行完成后,拦截器11111开始!!!!");
         long start = System.currentTimeMillis();
         log.info("请求执行完成后,拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 ​
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         log.info("视图渲染完成后,拦截器11111开始!!!!");
         long start = System.currentTimeMillis();
         log.info("视图渲染完成后,拦截器11111结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 }
 ​
 // -------------------------------------------------------------------------
 @Slf4j
 public class TestInterceptor2 implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         log.info("请求执行前拦截器22222开始!!!!");
         long start = System.currentTimeMillis();
         log.info("请求执行前拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
         return true;//当返回true的时候,才可以执行postHandle/afterCompletion
     }
 ​
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         log.info("请求执行完成后,拦截器22222开始!!!!");
         long start = System.currentTimeMillis();
         log.info("请求执行完成后,拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 ​
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
         log.info("视图渲染完成后,拦截器22222开始!!!!");
         long start = System.currentTimeMillis();
         log.info("视图渲染完成后,拦截器22222结束!!!!耗时:" + (System.currentTimeMillis() - start) + "ms");
     }
 }
View Code

aop切面

 @Aspect
 @Slf4j
 @Component
 public class TestAspect {
 ​
     /* Pointcut是织入Advice的触发条件 */
     @Pointcut("execution(* com.mmdz.spring.mvc.rest..*.*(..))") // rest下的任何方法
     public void pointcut() {
     }
 ​
     @Before("pointcut()")
     public void beforeControllerMethod(JoinPoint joinPoint) {
         log.info("前置通知!!!!方法执行前执行");
     }
 ​
     @Around("pointcut()")
     public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
         String uuIdTrace = UUID.randomUUID().toString().replace("-", "");
         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
         //IP地址
         String ipAddr = getRemoteHost(request);
 //        String url = request.getRequestURL().toString();
         String requestMethod = request.getMethod();
         String requestURI = request.getRequestURI();
         String reqParam = preHandle(proceedingJoinPoint, request);//获取请求参数
         log.info(uuIdTrace + " 环绕通知,执行方法前,请求源IP:【{}】,请求方法:【{}】,请求URL:【{}】,请求参数:【{}】", ipAddr, requestMethod, requestURI, reqParam);
         long start = System.currentTimeMillis();
         Object result = proceedingJoinPoint.proceed();// 执行方法,获取响应信息
         String respParam = postHandle(result);
         log.info(uuIdTrace + " 环绕通知,执行方法后,请求源IP:【{}】,请求URL:【{}】,返回参数:【{}】,执行耗时 : {}", ipAddr, requestURI, respParam, (System.currentTimeMillis() - start) + "ms");
         return result;
     }
 ​
     @After("pointcut()")
     public void afterControllerMethod(JoinPoint joinPoint) {
         log.info("后置通知:方法执行完后执行");
     }
 ​
     @AfterReturning(returning = "result", pointcut = "pointcut()")
     public void doAfterReturnint(Object result) {
         log.info("后置通知,方法执行完后执行,响应信息为:{}", JSONObject.toJSONString(result));
     }
 ​
     /**
      * 入参数据
      *
      * @param joinPoint
      * @param request
      * @return
      */
     private String preHandle(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
         try {
             Map<String, Object> fieldsName = getFieldsName(joinPoint);
             return JSON.toJSONString(fieldsName);
         } catch (Exception e) {
             log.warn("切面获取请求参数出现异常", e);
             return null;
         }
     }
 ​
     private Map<String, Object> getFieldsName(JoinPoint joinPoint) throws Exception {
         String classType = joinPoint.getTarget().getClass().getName();
         String methodName = joinPoint.getSignature().getName();
         // 参数值
         Object[] args = joinPoint.getArgs();
         Class<?>[] classes = new Class[args.length];
         for (int k = 0; k < args.length; k++) {
             // 对于接受参数中含有MultipartFile,ServletRequest,ServletResponse类型的特殊处理,我这里是直接返回了null。(如果不对这三种类型判断,会报异常)
             if (args[k] instanceof MultipartFile || args[k] instanceof ServletRequest || args[k] instanceof ServletResponse) {
                 return null;
             }
             if (!args[k].getClass().isPrimitive()) {
                 // 当方法参数是基础类型,但是获取到的是封装类型的就需要转化成基础类型
 //                String result = args[k].getClass().getName();
 //                Class s = map.get(result);
// 当方法参数是封装类型
                 Class s = args[k].getClass();
 ​
                 classes[k] = s == null ? args[k].getClass() : s;
             }
         }
         ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
         // 获取指定的方法,第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型
         Method method = Class.forName(classType).getMethod(methodName, classes);
         // 参数名
         String[] parameterNames = pnd.getParameterNames(method);
         // 通过map封装参数和参数值
         HashMap<String, Object> paramMap = new HashMap();
         for (int i = 0; i < parameterNames.length; i++) {
             paramMap.put(parameterNames[i], args[i]);
         }
         return paramMap;
     }
 ​
     /**
      * 返回数据
      *
      * @param retVal
      * @return
      */
     private String postHandle(Object retVal) {
         if (null == retVal) {
             return "";
         }
         return JSON.toJSONString(retVal);
     }
 ​
     /**
      * 获取目标主机的ip
      *
      * @param request
      * @return
      */
     private String getRemoteHost(HttpServletRequest request) {
         String ip = request.getHeader("x-forwarded-for");
         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
             ip = request.getHeader("Proxy-Client-IP");
         }
         if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("WL-Proxy-Client-IP"); 
        } 
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getRemoteAddr(); 
        } 
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; 
    } 
}
View Code

测试

浏览器访问

控制台输出

总结

执行顺序

当过滤器、拦截器、Aop都用于一个请求路径的接口方法时,执行顺序为 过滤器--->拦截器--->AOP

作用

过滤器的主要作用

  • 用户访问权限处理

  • 设置字符集乱码处理

  • 过滤敏感词汇、压缩响应信息

拦截器的主要作用

  • 只能拦截请求,可以访问上下文等对象,功能强大,一个请求可多次拦截。

  • 用户访问权限处理

  • 登记日志

AOP的主要作用

  • 日志记录

  • 性能统计

  • 安全控制

  • 事务处理

  • 异常处理

  • 只能应用于由 Spring 容器管理的 bean

一般情况下,数据被过滤拦截的时机越早对服务的性能影响越小,在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是AOP

posted @ 2022-12-14 15:04  梅子猪  阅读(476)  评论(0)    收藏  举报