1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。
    2、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理的方式来执行。
    3、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。

    过滤器的配置
    现在我们通过过滤器来实现记录请求执行时间的功能,其实现如下:

    1. public class LogCostFilter implements Filter {
    2. @Override
    3. public void init(FilterConfig filterConfig) throws ServletException {
    4. }
    5. @Override
    6. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    7. long start = System.currentTimeMillis();
    8. filterChain.doFilter(servletRequest,servletResponse);
    9. System.out.println("Execute cost="+(System.currentTimeMillis()-start));
    10. }
    11. @Override
    12. public void destroy() {
    13. }
    14. }

    这段代码的逻辑比较简单,就是在方法执行前先记录时间戳,然后通过过滤器链完成请求的执行,在返回结果之间计算执行的时间。这里需要主要,这个类必须继承Filter类,这个是Servlet的规范,这个跟以前的Web项目没区别。但是,有了过滤器类以后,以前的web项目可以在web.xml中进行配置,但是spring boot项目并没有web.xml这个文件,那怎么配置?在Spring boot中,我们需要FilterRegistrationBean来完成配置。其实现过程如下:

    1. @Configuration
    2. public class FilterConfig {
    3. @Bean
    4. public FilterRegistrationBean registFilter() {
    5. FilterRegistrationBean registration = new FilterRegistrationBean();
    6. registration.setFilter(new LogCostFilter());
    7. registration.addUrlPatterns("/*");
    8. registration.setName("LogCostFilter");
    9. registration.setOrder(1);
    10. return registration;
    11. }
    12. }

    大家可以看到上面的配置已经生效了。除了通过 FilterRegistrationBean 来配置以外,还有一种更直接的办法,直接通过注解就可以完成了:

    1. @WebFilter(urlPatterns = "/*", filterName = "logFilter2")
    2. public class LogCostFilter2 implements Filter {
    3. @Override
    4. public void init(FilterConfig filterConfig) throws ServletException {
    5. }
    6. @Override
    7. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    8. long start = System.currentTimeMillis();
    9. filterChain.doFilter(servletRequest, servletResponse);
    10. System.out.println("LogFilter2 Execute cost=" + (System.currentTimeMillis() - start));
    11. }
    12. @Override
    13. public void destroy() {
    14. }
    15. }

    可以看到,我们配置的两个过滤器都生效了。细心的读者会发现,第二个Filter我们并没有指定执行的顺序,但是却在第一个Filter之前执行。这里需要解释一下,@WebFilter这个注解并没有指定执行顺序的属性,其执行顺序依赖于Filter的名称,是根据Filter类名(注意不是配置的filter的名字)的字母顺序倒序排列,并且@WebFilter指定的过滤器优先级都高于FilterRegistrationBean配置的过滤器。有兴趣的朋友可以自己实验一下。

    拦截器的配置

    image.png

    1. /** 解决跨域问题 **/
    2. public void addCorsMappings(CorsRegistry registry) ;
    3. /** 添加拦截器 **/
    4. void addInterceptors(InterceptorRegistry registry);
    5. /** 这里配置视图解析器 **/
    6. void configureViewResolvers(ViewResolverRegistry registry);
    7. /** 配置内容裁决的一些选项 **/
    8. void configureContentNegotiation(ContentNegotiationConfigurer configurer);
    9. /** 视图跳转控制器 **/
    10. void addViewControllers(ViewControllerRegistry registry);
    11. /** 静态资源处理 **/
    12. void addResourceHandlers(ResourceHandlerRegistry registry);
    13. /** 默认静态资源处理器 **/
    14. void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);

    上面我们已经介绍了过滤器的配置方法,接下来我们再来看看如何配置一个拦截器。我们使用拦截器来实现上面同样的功能,记录请求的执行时间。首先我们实现拦截器类:

    1. public class LogCostInterceptor implements HandlerInterceptor {
    2. long start = System.currentTimeMillis();
    3. @Override
    4. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
    5. start = System.currentTimeMillis();
    6. return true;
    7. }
    8. @Override
    9. public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    10. System.out.println("Interceptor cost="+(System.currentTimeMillis()-start));
    11. }
    12. @Override
    13. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    14. }
    15. }

    这里我们需要实现HandlerInterceptor这个接口,这个接口包括三个方法,preHandle是请求执行前执行的,postHandler是请求结束执行的,但只有preHandle方法返回true的时候才会执行,afterCompletion是视图渲染完成后才执行,同样需要preHandle返回true,该方法通常用于清理资源等工作。除了实现上面的接口外,我们还需对其进行配置:

    1. @Configuration
    2. public class InterceptorConfig extends WebMvcConfigurerAdapter {
    3. @Override
    4. public void addInterceptors(InterceptorRegistry registry) {
    5. registry.addInterceptor(new LogCostInterceptor()).addPathPatterns("/**");
    6. super.addInterceptors(registry);
    7. }
    8. }

    这里我们继承了WebMVCConfigurerAdapter,看过前面的文章的朋友应该已经见过这个类了,在进行静态资源目录配置的时候我们用到过这个类。这里我们重写了addInterceptors这个方法,进行拦截器的配置,主要配置项就两个,一个是指定拦截器,第二个是指定拦截的URL。现在我们再启动系统访问任意一个URL:
    可以看到,我们通过拦截器实现了同样的功能。不过这里还要说明一点的是,其实这个实现是有问题的,因为preHandle和postHandle是两个方法,所以我们这里不得不设置一个共享变量start来存储开始值,但是这样就会存在线程安全问题。当然,我们可以通过其他方法来解决,比如通过ThreadLocal就可以很好的解决这个问题,有兴趣的同学可以自己实现。不过通过这一点我们其实可以看到,虽然拦截器在很多场景下优于过滤器,但是在这种场景下,过滤器比拦截器实现起来更简单。