Java过滤器与拦截器的区别

1. 过滤器与拦截器概述

1.1 过滤器 Filter

依赖于servlet容器,实现基于函数回调,可以对几乎所有请求进行过滤,
但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,
获取我们想要获取的数据,过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、
字符编码转换等等操作,便于代码重用,不必每个servlet中进行冗余操作。

Java中的Filter并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。 
主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理,是个典型的处理链。
完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,
最后Filter再对服务器响应进行后处理。在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest。
根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据。在响应到达客户端之前,拦截HttpServletResponse。根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。

Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。
1.启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
2.每一次请求时都只调用方法doFilter()进行处理;
3.停止服务器时调用destroy()方法,销毁实例。

1.2 拦截器 interceptor

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。
在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。
由于拦截器是基于web框架的调用,拦截器可以调用IOC容器中的各种依赖,而过滤器不能,
因此可以使用Spring的依赖注入进行一些业务操作,
同时一个拦截器实例在一个controller生命周期之内可以多次调用。
但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

spring mvc中的Interceptor可以理解为是Spring MVC框架对AOP的一种实现方式。
一般简单的功能又是通用的,每个请求都要去处理的,比如判断token是否失效可以使用spring mvc的HanlderInterceptor, 
复杂的,比如缓存,需要高度自定义的就用spring aop。一般来说service层更多用spring aop,
controller层有必要用到request和response的时候,可以用拦截器。

spring mvc中的Interceptor拦截请求是通过HandlerInterceptor来实现的。
所以HandlerInteceptor拦截器只有在Spring Web MVC环境下才能使用。
在SpringMVC中定义一个拦截器主要有两种方式,第一种方式是要实现Spring的HandlerInterceptor接口,
或者是其它实现了HandlerInterceptor接口的类,比如HandlerInterceptorAdapter。
第二种方式是实现WebRequestInterceptor接口,或者其它实现了WebRequestInterceptor的类。

HandlerInterceptor接口定义方法preHandle, postHandle, 和afterCompletion:
preHandle(进入 Handler方法之前执行):预处理回调方法,实现处理器的预处理(如登录检查),返回值:true表示继续流程
(如调用下一个拦截器或处理器),false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,
此时我们需要通过response来产生响应。
postHandle(进入handler方法之后,返回modelAndView之前):后处理回调方法,
实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView
(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。

afterCompletion(执行Handler完成执行此方法):整个请求处理完毕回调方法,即在视图渲染完毕时回调。
该方法也是需要当前对应的Interceptor 的preHandle方法的返回值为true时才会执行。
这个方法的主要作用是用于进行资源清理工作的,如性能监控中我们可以在此记录结束时间并输出消耗时间。

2. 过滤器与拦截器区别

1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于Java的反射机制(动态代理)实现的。

2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。

3、触发时机不同
过滤器 和 拦截器的触发时机也不同,我们看下边这张图。
过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
在这里插入图片描述
4、拦截的请求范围不同
过滤器的init()方法,随着容器的启动进行了初始化。
执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后
过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。

5、注入Bean情况不同
在拦截器与过滤器中分别注入service进行逻辑处理,拦截器会空指针:
原因:拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。「注意」:在registry.addInterceptor()注册的是getMyInterceptor() 实例。

6、控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

注意:
先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

原因:
得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。
看看两个方法applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。
发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()、preHandle() 方法执行的顺序相反。

3. 过滤器与拦截器实现

3.1 过滤器(Filter)

package com.example.category.aop;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * 过滤器(Filter)
 * 注意:
 * init与destroy方法不是所必须实现的,可以选择或者手动实现
 * 启动类如果不加@ServletComponentScan就算这个类和运行类平级或者子级也不会被扫描上
 * filterName可以省略不能重复@WebFilter(filterName = "myFilterTwo"),
 *
 * @author zrj
 * @since 2021/8/17
 **/
@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyFilter implements Filter {
    
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    
    
        log.info("[过滤器]过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    
    
        log.info("[过滤器]过滤器执行过滤操作");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
    
    
        log.info("[过滤器]过滤器销毁");
    }
}

3.2 拦截器 (Interceptor)

package com.example.category.aop;

import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 拦截器 (Interceptor)
 * HandlerInterceptor:springMVC框架拦截器
 * 必须在WebMvc配置以后生效
 *
 * @author zrj
 * @since 2021/8/17
 **/
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
    
    
    /**
     * 前置拦截器
     * 如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        log.info("[拦截器]前置拦截器preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    
    
        log.info("[拦截器]处理拦截器postHandle");
    }

    /**
     * 后置拦截器
     * afterCompletion:只有在 preHandle() 方法返回值为true 时才会执行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    
    
        log.info("[拦截器]后置拦截器afterCompletion");
    }
}

3.3 拦截器WebMvc配置

package com.example.category.config;

import com.example.category.aop.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * WebMvc配置
 * 配置自定义拦截器
 *
 * @author zrj
 * @since 2021/8/17
 **/
@Configuration
public class CommonWebConfig implements WebMvcConfigurer {
    
    

    @Resource
    private MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(myInterceptor);
    }

}

3.4 切片(Aspect)

package com.example.category.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 切片(Aspect)
 *
 * @author zrj
 * @since 2021/8/17
 **/
@Slf4j
@Aspect
@Component
public class MyAspect {
    
    
    // 定义切点位置
    private final String POINT_CUT = "execution(* com.example.category.controller..*.*(..))";
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * 定义切点位置:下面如果你在SSM中用AOP,在xml中配的就是下面
     */
    @Pointcut(POINT_CUT)
    public void pointCut() {
    
    
    }

    /**
     * 前置通知
     *
     * @param joinPoint 连接点
     * @throws Throwable
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
    
    
        log.info("[切片]前置通知@Before");
        //获取目标方法参数信息
        Object[] args = joinPoint.getArgs();
        Arrays.stream(args).forEach(arg -> {
    
    
            try {
    
    
                log.info(OBJECT_MAPPER.writeValueAsString(arg));
            } catch (JsonProcessingException e) {
    
    
                log.info(arg.toString());
            }
        });
    }

    /**
     * 后置返回通知
     * 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
     * 参数为Object类型将匹配任何目标返回值
     */
    @AfterReturning(value = POINT_CUT, returning = "result")
    public void doAfterReturningAdvice1(JoinPoint joinPoint, Object result) {
    
    
        log.info("[切片]后置返回通知@AfterReturning,第一个的返回值:" + result);
    }

    @AfterReturning(value = POINT_CUT, returning = "result", argNames = "result")
    public void doAfterReturningAdvice2(String result) {
    
    
        log.info("[切片]后置返回通知@AfterReturning,第二个的返回值:" + result);
    }

    /**
     * 后置异常通知
     * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
     * throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
     * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     *
     * @param joinPoint 连接点
     * @param exception 异常
     */
    @AfterThrowing(value = POINT_CUT, throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
    
    
        log.info("[切片]后置异常通知@AfterThrowing");
        log.info(joinPoint.getSignature().getName());
        if (exception instanceof NullPointerException) {
    
    
            log.info("发生了空指针异常!!!!!");
        }
    }

    /**
     * 后置通知
     *
     * @param joinPoint 连接点
     * @return void
     */
    @After(value = POINT_CUT)
    public void doAfterAdvice(JoinPoint joinPoint) {
    
    
        log.info("[切片]后置通知@After");
    }

    /**
     * 环绕通知:
     * 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around(value = POINT_CUT)
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
    
    
        log.info("[切片]环绕通知@Around开始," + proceedingJoinPoint.getSignature().toString());
        Object obj = null;
        try {
    
    
            obj = proceedingJoinPoint.proceed(); //可以加参数
            log.info("[切片]环绕通知@Around执行" + obj.toString());
        } catch (Throwable throwable) {
    
    
            throwable.printStackTrace();
        }
        log.info("[切片]环绕通知@Around结束");
        return obj;
    }

}

4. 过滤器与拦截器执行顺序验证

-> 过滤器doFilter -> 前置拦截器preHandle
-> 环绕通知@Around开始 -> 前置通知@Before
-> UserController
-> 后置返回通知@AfterReturning -> 后置通知@After
-> 环绕通知@Around执行 ->环绕通知@Around结束
-> 处理拦截器postHandle -> 后置拦截器afterCompletion

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_37583655/article/details/121234046