Spring 过滤器和拦截器的区别

[toc]

过滤器(Filter)

过滤器有三个基础方法:initdoFilterdestroyinit 过滤器 Bean 实例化时调用。doFilter 在 Action 请求到达时进入,可以实现过滤逻辑,通过 FilterChain 可以继续处理请求。destroy 在 Bean 销毁时调用。

拦截器是基于函数回调,由 Servlet 容器实现的。可以对几乎所有请求进行过滤。过滤器的执行在拦截器之前,不能访问 Action 上下文信息。

使用过滤器可以完成一些过滤操作,对传入的 request、response 过滤或者设置一些参数,然后再传入 Servlet 或者 Controller 进行业务逻辑操作。

使用

实现过滤器

实现 javax.servlet.Filter 接口,添加 @WebFilter 注解,设置过滤器生效路径以及一些参数

@Order 注解指定过滤器应用顺序(数值小的先执行)

@Component
@Order(1)
@WebFilter(urlPatterns = "/**")
public class FilterX implements Filter {

  @Override
  public void init(final FilterConfig filterConfig) throws ServletException {
    // Filter Bean 实例化时调用
  }

  @Override
  public void doFilter(final ServletRequest request, final ServletResponse response,
      final FilterChain chain)
      throws IOException, ServletException {
    // 进入 Filter 方法,实现 Filter 逻辑

    // 通过 FilterChain 继续处理请求,多个 Filter 链式调用
    chain.doFilter(request, response);

    // 请求处理结束后,退出 Filter 方法
  }

  @Override
  public void destroy() {
    // Filter 销毁时调用
  }
}

拦截器(Interceptor)

拦截器有三个基础方法:preHandlepostHandleafterCompletionpreHandle 在进入 Action 处理前调用,如果返回 true,继续处理,返回 false 则中断。postHandle 在 Action 处理完成后调用,如果 Action 抛出异常则不会调用。afterCompletionpostHandle 完成后或 Action 抛出异常时调用。

拦截器是基于 Java 的反射机制,由 Web 框架实现的,不依赖 Servlet 容器,对 Action 请求的生命周期内会多次调用,可以访问 Action 的上下文信息。只能对 Controller 请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

应用场景

拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:

  • 登录验证,判断用户是否登录。
  • 权限验证,判断用户是否有权限访问资源。
  • 后端埋点,统计PV/UV,监控处理时长。
  • 日志记录,记录请求操作日志,审计。
  • 处理Cookie、本地化、国际化、主题等。

使用

实现拦截器

实现 org.springframework.web.servlet.HandlerInterceptor 接口

通常会实现 org.springframework.web.servlet.handler.HandlerInterceptorAdapter 接口

preHandle() 方法返回 true,请求会继续处理。

如果使用 Feign,拦截 Feign 调用可以实现 feign.RequestInterceptor

如果使用 RestTemplate,拦截 RestTemplate 请求可以实现 org.springframework.http.client.ClientHttpRequestInterceptor

@Component
public class InterceptorX implements HandlerInterceptor {

  @Override
  public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,
      final Object handler) throws Exception {
    // return true,继续处理请求
    return true;
  }

  @Override
  public void postHandle(final HttpServletRequest request, final HttpServletResponse response,
      final Object handler, final ModelAndView modelAndView) throws Exception {
    // 请求处理完成后进入
    // 请求抛出异常不会进入
  }

  @Override
  public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
      final Object handler, final Exception ex) throws Exception {
    // 请求处理完成,并且所有拦截器的 postHandle 处理完成后进入
    // 或者请求处理异常后进入
  }
}

注册拦截器

实现拦截器后需要将拦截器注册到容器中,可以通过实现 WebMvcConfigurer,重写 addInterceptors(InterceptorRegistry registry) 方法。

@Configuration
public class Application implements WebMvcConfigurer {

  @Autowired
  public InterceptorX interceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 先 add 的拦截器先生效,链式调用
    registry.addInterceptor(interceptor).addPathPatterns("/**");
    registry.addInterceptor(new InterceptorX()).addPathPatterns("/**");
  }
}

源代码

过滤器相关代码

使用职责链设计模式

class StandardWrapperValve extends ValveBase {

  public final void invoke(Request request, Response response) {
    // ...

    // 创建 FilterChain
    ApplicationFilterChain filterChain =
      ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // 进入 Filter 处理逻辑
    filterChain.doFilter(request.getRequest(), response.getResponse());
  }
}

class ApplicationFilterChain implements FilterChain {
  private int pos = 0;
  private ApplicationFilterConfig[] filters;

  public void doFilter(ServletRequest request, ServletResponse response) {
    internalDoFilter(request,response);
  }

  private void internalDoFilter(ServletRequest request,
                                ServletResponse response) {
    // ...

    ApplicationFilterConfig filterConfig = filters[pos++];
    Filter filter = filterConfig.getFilter();
    filter.doFilter(request, response, this);

    // ...
  }
}

FilterChain 的创建

class ApplicationFilterFactory {
  public static ApplicationFilterChain createFilterChain(ServletRequest request,
                                                         Wrapper wrapper, Servlet servlet) {

        ApplicationFilterChain filterChain = null;
            filterChain = new ApplicationFilterChain();

            Request req = (Request) request;
            req.setFilterChain(filterChain);

            // 这里 set servlet,后面会用到
        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

        // 获取 FilterMap,找到提前注册好的 Filter
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        // 把跟本次请求相关的 Filter 加入 FilterChain
        // URL 匹配,Servlet 匹配 
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher))
                continue;
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            // 源码这里进行了两次 for 循环
            // if (!matchFiltersServlet(filterMaps[i], servletName))
            //    continue;

            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            // 加入 ApplicationFilterConfig[] filters
            filterChain.addFilter(filterConfig);
        }

        return filterChain;
    }
}

拦截器相关代码

紧跟着过滤器的处理之后

class ApplicationFilterChain implements FilterChain {
  // createFilterChain 时设置的
  private Servlet servlet = null;

  private void internalDoFilter(ServletRequest request,
                                ServletResponse response) {
    // ...
    filter.doFilter(request, response, this);

    // 进入 Servlet 处理请求
    servlet.service(request, response);

  } 
}

// 会进入到这里
class DispatcherServlet extends FrameworkServlet {
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
    // ...

    // 获取到 HandlerExecutionChain
    HandlerExecutionChain mappedHandler = null;
    mappedHandler = getHandler(processedRequest);

    try {
      // 执行 preHandle,返回 false 退出
      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
      }

      //... 请求处理,这里不详细介绍
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

      // 执行 posthandle
      mappedHandler.applyPostHandle(processedRequest, response, mv);

      // 这里也会调用 triggerAfterCompletion
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Throwable t) {
      // 异常会调用 triggerAfterCompletion
      triggerAfterCompletion(processedRequest, response, mappedHandler, t);
    }
  }
}

HandlerExecutionChain

以 applyPreHandle 举例,获取到 HandlerInterceptor 数组,顺序执行

class HandlerExecutionChain {
  boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();

    for (int i = 0; i < interceptors.length; i++) {
      HandlerInterceptor interceptor = interceptors[i];
      if (!interceptor.preHandle(request, response, this.handler)) {
        // 触发 afterCompletion
        triggerAfterCompletion(request, response, null);
        return false;
      }
      this.interceptorIndex = i;
    }
        return true;
    }
}

处理流程图

image-20210630231029917

总结

拦截器和过滤器的区别:

  1. 拦截器是基于 Java 的反射机制的,而过滤器是基于函数回调。
  2. 拦截器不依赖与 servlet 容器,过滤器依赖 servlet 容器。
  3. 拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用。
  4. 拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问。
  5. 在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
  6. 拦截器基本可以实现过滤器的功能。

执行顺序 :

假设有 A、B 两个 Filter,C、D 两个 Interceptor,一次请求的处理顺序是:

进入 FilterA 代码 -> 
进入 FilterB 代码 ->
执行 InterceptorC#preHandle -> 
执行 InterceptorD#preHandle ->
处理 Action(进入 Controller) -> 
执行 InterceptorD#postHandle -> 
执行 InterceptorC#postHandle ->
执行 InterceptorD#afterCompletion -> 
执行 InterceptorC#afterCompletion -> 
退出 FilterB 代码 -> 
退出 FilterA 代码
Tags:,

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注