拦截器与过滤器

Posted by Pismery Liu on Saturday, January 4, 2020

TOC

过滤器与拦截器

本篇文章,主要讲解过滤器与拦截器的区别,使用方式以及使用场景;

过滤器与拦截器的区别:

  • 使用范围不同:过滤器 (filter) 是 Java Sevlet 规范中的一部分的,因此只能用于 web 程序;而拦截器 (interceptor) 是 Spring 提供的组件,不依赖 Servlet 容器,可用于 Web ,Application,Swing 程序;
  • 可使用资源不同:拦截器可直接注入 Spring 容器对象,即拦截器可以注入 Server 执行业务操作,而过滤器无法注入 Spring 容器对象;
  • 实现方式不同:过滤器是基于函数回调的方式实现;拦截器是通过反射机制实现;
  • 调用时机不同:

常见使用场景

  • 统一请求字符串编码方式;
  • 记录访问日志;
  • 权限认证,token 检测;
  • 接口执行时间检测;

过滤器

过滤器有多种配置方式,下面演示其中两种方式: Bean 配置方式和注解配置方式

Bean 配置方式

操作步骤:

  1. 编写 Filter 类实现 javax.servlet.Filter 类;
  2. 编写配置类,注册 FilterRegistrationBean,并将 filter 配置信息配置其中;
@Configuration
public class FilterConfig implements WebMvcConfigurer {
    @Bean
    public FilterRegistrationBean<AFilter> aFilterRegistration() {
        FilterRegistrationBean<AFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new AFilter());
        // 过滤应用程序中所有资源,当前应用程序根下的所有文件包括多级子目录下的所有文件;
        registration.addUrlPatterns("/a-filter/*"); // 不用加上server.servlet.context-path;
        // 可通过 FilterConfig 对象获取设置的 Parameter 值
        registration.addInitParameter("aFilter", "pis-a-filter");
        registration.setName("AFilter");

        return registration;
    }
}
@Slf4j
public class AFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AFilter init!...");
        log.info("AFilter Param: " + filterConfig.getInitParameter("aFilter"));

    }

    @Override
    public void destroy() {
        log.info("AFilter destroy!...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("AFilter before invoke doFilter!...");
        chain.doFilter(request, response);
        log.info("AFilter after invoke doFilter!...");
    }
}

注解配置

操作步骤:

  1. 编写 Filter 类实现 javax.servlet.Filter 类;
  2. 使用 @WebFilter 注解配置 Filter 相应信息;
  3. 在启动类中加上注解 @ServletComponentScan,用于扫描发现 Servlet 规范组件;
@Slf4j
@WebFilter(
        urlPatterns = "/b-filter/*"
        , displayName = "BFilter"
        , initParams = {@WebInitParam(name = "bFilter", value = "pis-b-filter")}
)
public class BFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("BFilter init!...");
        log.info("BFilter Param: "+filterConfig.getInitParameter("bFilter"));
    }

    @Override
    public void destroy() {
        log.info("BFilter destroy!...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("BFilter before invoke doFilter!...");
        chain.doFilter(request, response);
        log.info("BFilter after invoke doFilter!...");
    }
}

@SpringBootApplication
@ServletComponentScan //用于扫描 Servlet 规范的组件,如 Filter, Servlet, Listener
public class InterceptorFilterApplication {
    public static void main(String[] args) {
        SpringApplication.run(InterceptorFilterApplication.class, args);
    }
}

多个 Filter 指定执行顺序

当对于某一个请求,存在多个 filter 时,其默认执行顺序是按照 Filter Bean 的名称升序排序方式;也可通过 Bean 配置方式,显示设置 Order 值;Order 值越小越优先执行;注意:通过注解 @Order 是无法设置 Filter 执行顺序的

@Configuration
public class FilterConfig implements WebMvcConfigurer {
    @Bean
    public FilterRegistrationBean<CFilter> cFilterRegistration() {
        FilterRegistrationBean<CFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter("cFilter", "pis-c-filter");
        registration.setName("CFilter");
        // 设置执行顺序,否则按 Bean 的名字排序
        registration.setOrder(1);

        return registration;
    }
}
@Slf4j
public class CFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("CFilter init!...");
        log.info("CFilter Param: " + filterConfig.getInitParameter("cFilter"));
    }

    @Override
    public void destroy() {
        log.info("CFilter destroy!...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("CFilter before invoke doFilter!...");
        chain.doFilter(request, response);
        log.info("CFilter after invoke doFilter!...");
    }
}

执行结果

启动 Spring boot 服务器,访问 http:xxx/a-filter,执行结果如下

从结果可以看到,由于显示设置了 CFilter 的 Order 为 1 优先执行;所以 CFilter 优先执行,而如果按照 Bean 名称则应该 AFilter 优先执行,所以配置生效;

两种配置方式对比

Bean 配置方式

配置更加灵活,能够指定执行顺序和引入第三方类库中的过滤器;

注解配置方式

配置更加简单,并且代码处于同一个位置,更好维护;但无法指定 Order 即不能调整过滤器的执行顺序,只能按照过滤器名称顺序执行;如果需要配置第三方类库中的过滤器,则无法使用注解方式;

拦截器

Bean 配置方式

操作步骤:

  1. 编写 Interceptor 类实现 org.springframework.web.servlet.HandlerInterceptor 类;
  2. 编写配置类,实现 org.springframework.web.servlet.config.annotation.WebMvcConfigurer 接口中的 addInterceptors 方法;
@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //如果想指定某一个 url 如 login 接口,不能用 /web/login, /*/login; 需要使用 /**/login/**
        //添加顺序即为拦截器的执行顺序;
        registry.addInterceptor(new AInterceptor()).addPathPatterns("/**/a-interceptor/**");
        registry.addInterceptor(new CInterceptor()).addPathPatterns("/**");
    }
}
@Slf4j
public class AInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("AInterceptor preHandle!...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("AInterceptor postHandle!...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("AInterceptor afterCompletion!...");
    }
}

@Slf4j
public class CInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("CInterceptor preHandle!...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("CInterceptor postHandle!...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("CInterceptor afterCompletion!...");
    }
}

执行结果

启动 Spring boot 服务器,访问 http:xxx/a-interceptor,执行结果如下

@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AInterceptor()).addPathPatterns("/**/a-interceptor/**");
        registry.addInterceptor(new CInterceptor()).addPathPatterns("/**");
    }
}

调整添加 Interceptor 顺序,重新测试拦截器执行顺序,执行结果如下:

@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new AInterceptor()).addPathPatterns("/**/a-interceptor/**");
    }
}

参考链接