从源码角度结合详细图例剖析过滤器与拦截器

x33g5p2x  于2021-11-15 转载在 其他  
字(8.5k)|赞(0)|评价(0)|浏览(210)

前言

在工作中,我们经常使用到过滤器与拦截器,但可能对他们的原理以及区别不是特别清楚。

今天的这篇文章,会介绍一下过滤器与拦截器的简单使用,从源码角度窥探两者的执行顺序与实现原理以及从不同角度阐述它们之间的区别。

过滤器使用

@Component
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("Filter.init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Filter.doFilter.pre");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("Filter.doFilter.post");
    }

    @Override
    public void destroy() {
        System.out.println("Filter.destroy");
    }
    
}

自定义一个过滤器,需要实现Filter接口,实现以下3个接口方法

  • init方法,接口中的默认方法。容器初始化过滤器时会被调用,且只会被调用一次。
  • doFilter方法,过滤器的核心方法,必须实现。在该方法内完成对请求的处理,并通过 filterChain.doFilter 调用下一个过滤器。
  • destroy方法,接口中的默认方法。过滤器在销毁前会执行,当然也只会被调用一次。

先简简单单创建一个Controller接口,观察一下调用链路:

@GetMapping("/test")
    public String test() {
        System.out.println("进入Controller");
        return "SUCCESS";
    }

 拦截器使用

@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor.preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor.postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor.afterCompletion");
    }

}

实现一个自定义的拦截器,需要实现HandlerInterceptor接口,并实现以下3个方法(都是默认方法)

  • preHandle方法,在到达controller方法之前调用。需要注意的是,一旦preHandle返回false,则代表之后的所有拦截器与Controller都不会再执行。
  • postHandle方法,在controller方法执行之后调用,在DispatcherServlet渲染视图之前调用。
  • afterCompletion方法,在DispatcherServlet渲染视图之后调用。

当然,如果要使用该拦截器,还需要在指定路径上配置该拦截器,使得不同的拦截器作用于不同路径上。

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }
    
}

调用链路:

过滤器源码

通过多次调试发现,每一个请求,会进入到org.apache.catalina.core.StandardWrapperValve的invoke方法中。

invoke方法,省略了亿点点无关内容(以下源码部分,为了缩减篇幅,都省略了部分代码):

public final void invoke(Request request, Response response) throws IOException, ServletException {
        //为当前请求创建filterChain
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        //执行filterChain的doFilter方法
        if ((servlet != null) && (filterChain != null)) {
            filterChain.doFilter(request.getRequest(), response.getResponse());
        } 
    }

invoke方法中,会为每一个请求创建ApplicationFilterChain实例,接着执行该实例的doFilter方法。

进入到createFilterChain:

public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
        //初始化ApplicationFilterChain实例
        ApplicationFilterChain filterChain = new ApplicationFilterChain();
        //设置Servlet
        filterChain.setServlet(servlet);

        //获取容器中所有的过滤器
        //filterMaps中存放的是来自于web.xml中配置的过滤器以及Spring所管理的过滤器
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        String servletName = wrapper.getName();

        //按照路径匹配,匹配成功则添加到filterChain中
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i], dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
            filterChain.addFilter(filterConfig);
        }

        //按servlet名称匹配,匹配成功则添加到filterChain中
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i], dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
            filterChain.addFilter(filterConfig);
        }

        return filterChain;
    }

createFilterChain方法中,为该请求创建了一个ApplicationFilterChain 实例,并为其设置servlet、添加符合匹配规则的过滤器。

接着invoke方法调用ApplicationFilterChain实例的doFilter方法,而doFilter内部仅调用了internalDoFilter方法:

private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        //pos为filterChain中正在执行的过滤器的下标
        //n为filterChain中过滤器的个数
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
            //执行过滤器的doFilter方法,并传入当前filterChain实例
            filter.doFilter(request, response, this);
            return;
        }

        //filterChain中所有的过滤器执行完毕后,执行servlet的service方法
        servlet.service(request, response);
    }

internalDoFilter首先会执行第一个过滤器的doFilter方法,传入request、response与当前filterChain实例。

此过滤器在doFilter在结束之前,会执行filterChain.doFilter,于是又回到了internalDoFilter方法中。

internalDoFilter在执行完所有的过滤器后,会执行servlet.service,service方法最终会进入到DispatcherServlet的doDispatch内。

假设当前一共有两个过滤器,则执行流程为:

过滤器其实是责任链模式的一种典型实现,通过滤器对filterChain的回调,来把控制权继续交给filterChain。由filterChain选择是继续执行下一个过滤器,还是执行service方法。

值得注意的是,如果某个过滤器没有执行filterChain.doFilter方法,即没有执行回调,则该请求就不会被其他过滤器处理,也不会进入到Controller中。

拦截器源码

当执行完所有的过滤器后,filterChain会引导进入servlet的service方法中,service最终会调用DispatcherServlet的doDispatch方法。

在doDispatch方法中,同样省略部分无关代码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;

        //....

        ModelAndView mv = null;
        Exception dispatchException = null;

        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);

        //1.从处理器映射中获取处理器执行链,包含一个主要的处理器以及拦截器
        mappedHandler = getHandler(processedRequest);

        //2.由处理器获得处理器适配器
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        //3.调用拦截器的preHandle
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        //4.真正处理请求的逻辑,返回ModelAndView对象
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

        //5.调用拦截器的postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);

        //6.将逻辑视图转化为物理视图,渲染视图后,调用拦截器的afterCompletion
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

在第3步真正处理请求前,会先调用applyPreHandle方法:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

applyPreHandle中的逻辑比较简单,按照顺序挨个调用拦截器的preHandle方法。

值得注意的是,当其中某个拦截器的preHandle返回false,则执行所有拦截器的afterCompletion。接着返回false,使得doDispatch方法直接返回。剩余的拦截器与Controller方法就不会再执行。

第4步为真正处理请求的逻辑,内部会调用Controller方法。

第6步执行applyPostHandle方法:

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = interceptors.length - 1; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				interceptor.postHandle(request, response, this.handler, mv);
			}
		}
	}

比较有趣的是,这里会反过来执行所有拦截器的postHandle方法。也就是说,最先调用preHandle的拦截器,将最后调用其postHandle方法。

第6步在渲染视图后,会执行triggerAfterCompletion方法:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}

和applyPostHandle一样,调用拦截器的afterCompletion方法也是反着来的。

因此,如果对当前路径配置了两个拦截器,则执行的流程为:

 二者区别

从第一节的调用链路来看,如果我需要实现请求鉴权、请求日志记录或请求时间统计,则过滤器与拦截器都可以选择。

既然两者存在功能上的相似之处,那他们的区别到底在哪里呢?

使用的范围或规范不同

过滤器需要实现Filter接口,而该接口被定义javax.servlet包下。也就是说,过滤器是依赖Servlet容器的,因此过滤器只能在Web程序中使用。

拦截器需要实现HandlerInterceptor接口,而该接口被定义在org.springframework.web.servlet包下。也就是说,拦截器是受Spring管理的,能够使用Spring框架的项目,都能使用到拦截器。

使用到的资源不同

如果过滤器仅使用web.xml配置,而没使用@Component注解的话,且不做其他特殊处理,那么过滤器就无法使用Spring容器中各种Bean资源以及Service对象,而拦截器就可以使用。

这是因为过滤器被Tomcat容器所管理,在没有被Spring容器管理的前提下,就无法使用Spring中的资源。

实现前后置处理的原理不同

过滤器实现前置处理与后置处理的能力来自于职责链,通过filterChain.doFilter的摆放位置,将过滤逻辑一切为二。前面的代码为前置处理,后面的代码则为后置处理。先执行前置处理的过滤器,其后置处理将会最晚处理,和递归调用很像。

拦截器实现前置后置处理的能力则来自于在for循环中手动改变调用顺序,先按正序依次调用preHandle,再反序调用postHandle。

触发时机不同

过滤器在请求进入Servlet前进行前置处理,在请求离开Servlet后进行后置处理。

Controller层出现异常后,过滤器的后置处理将不会再执行。

拦截器在请求进入Servlet后,且在进入Controller进行前置处理,在离开Controller后进行后置处理。

不论Controller层是否出现异常,拦截器的afterCompletion一定会被执行,所以拦截器的处理能力相比过滤器更加全面与立体。

如果不考虑源码调用细节,触发时机如图所示:

如果有多个过滤器与拦截器,并且考虑源码调用细节:

如果结合SpringMVC的核心流程doDispatch来看:

相关文章