参考文档:

Spring 拦截器和过滤器的区别?

1. 实现原理不同

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

这里重点说下过滤器!

在我们自定义的过滤器中都会实现一个 doFilter() 方法,这个方法有一个 FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain 是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain 里面能拿到我们自定义的 xxxFilter 类,在其内部回调方法 doFilter() 里调用各个自定义 xxxFilter 过滤器,并执行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
}

而每个 xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行 filterChain.doFilter(servletRequest, servletResponse),也就是回调 ApplicationFilterChain 的 doFilter() 方法,以此循环执行实现函数回调。

2. 使用范围不同

我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于Tomcat 等容器,导致它只能在 web 程序中使用。

而拦截器 (Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。

3. 触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图。

v2-66c2ba948283c44f9e07d6af8933caf9_1440w3. 触发时机不同

过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。

拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

执行顺序为:

Filter 处理中 (chain.doFilter 前)
Interceptor 前置 (preHandle)
Controller
Interceptor 处理中 (postHandle)
Interceptor 后置 (afterCompletion)
Filter 处理中 (chain.doFilter 后)

4. 请求范围不同

  • 过滤器 Filter 执行了两次,拦截器 Interceptor 只执行了一次。
  • 过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对 Controller 中请求或访问 static 目录下的资源请求起作用。

5. 过滤器实现方式

5.1 filter 注入容器

@Component // 加入容器会自动可用
@Slf4j
public class MyTestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyTestFilter doFilter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

5.2 FilterRegistrationBean

@Slf4j
public class MyTestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyTestFilter doFilter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyTestFilter> testFilterRegistration() {
        FilterRegistrationBean<MyTestFilter> registration = new FilterRegistrationBean<>(new MyTestFilter());
        registration.addUrlPatterns("/test/*");
        registration.setName("testFilter");
        registration.setOrder(1);
        return registration;
    }
}

5.3 @WebFilter

@Slf4j
@WebFilter(urlPatterns = "/test/*", filterName = "testFilter")
public class MyTestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyTestFilter doFilter");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

6. 拦截器实现

public class MyTestInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("preHandle");
        return true;
    }
}
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyTestInterceptor()).addPathPatterns("/test/**");
        super.addInterceptors(registry);
    }
}

7. 拦截器无法注入 bean 处理

参考 为什么你写的拦截器注入不了 Java bean?

如下代码:

@Component
public class MyTestInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private ServiceA serviceA;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("preHandle");
        serviceA.methodC();
        return true;
    }
}
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyTestInterceptor()).addPathPatterns("/test/**");
        super.addInterceptors(registry);
    }
}

我们会发现拦截器注入的 serviceA 为 null. 因为我们添加拦截器的时候,使用的是 new, 这个拦截器并没有加入 spring 容器,无法注入。

解决方法

将拦截器交给 spring ioc 管理即可

@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
    @Autowired
    private MyTestInterceptor myTestInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myTestInterceptor).addPathPatterns("/test/**");
        super.addInterceptors(registry);
    }
}

8. 控制执行顺序

过滤器

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用 @Order 注解控制执行顺序,通过 @Order 控制过滤器的级别,值越小级别越高越先执行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {}

拦截器

拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

@Override
public void addInterceptors(InterceptorRegistry registry) {
	registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
	registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
	registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}

看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

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

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后