1

过滤器和拦截器的辨析 - VoidCm

 1 month ago
source link: https://www.cnblogs.com/void-cmy/p/18065471
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
1.过滤器和拦截器的辨析03-11

过滤器和拦截器的辨析

介绍#

过滤器和拦截器都是为了在请求到达目标处理器(Servlet或Controller)之前或者之后插入自定义的处理逻辑

  • 过滤器:

遵循AOP(面向切面编程)思想实现,基于Servlet规范提供的Filter接口,它是位于客户端请求与服务器响应之间的一个组件,依赖于Servlet容器。当请求到达服务器时,过滤器会在请求进入实际目标资源(如Servlet、JSP页面)之前或之后执行特定的操作,原理是基于函数回调

  • 拦截器

遵循AOP(面向切面编程)思想实现,如Spring MVC中的HandlerInterceptor接口,它不依赖于Servlet容器的具体实现,而是由应用框架管理。拦截器是在请求进入到控制器层(Controller)方法前后执行自定义逻辑

原理解析#

过滤器#

为什么说过滤器基于函数回调?

过滤器基于函数回调,所谓函数回调/回调函数指的是:一个函数(称为回调函数)作为参数传递给另一个函数(称为调用函数),当满足一定条件或者在某个特定时刻,调用函数会调用传递过来的回调函数

由于Java中不直接支持函数指针,所以常常通过接口来实现回调机制

FilterChain就是一个接口

public interface FilterChain {

    public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;

}

Filter的实现类中doFilter()方法中FilterChain作为参数被传进来,并且在合适的时机被回调了其doFilter方法

public class MyFilter 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 {
        log.info("hello");
        chain.doFilter(request,response);
    }

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

拦截器#

拦截器基于AOP(面向切面编程)思想实现,但是并不一定用到动态代理或者切面,切点之类的技术,以如Spring MVC中的HandlerInterceptor接口为例,从源码看更像是直接将拦截器注入,形成了一个拦截器链,在controller层面上进行代码织入

DispatcherServlet作为SpringMVC框架的核心类,http请求的核心执行方法为doService(),再进入doDispatch()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
        /**
         * 1.
         * HandlerExecutionChain 是一个对象
         * 包含了以下重要的属性
         * private final Object handler; //处理器(controller和其最后的方法)
         * List<HandlerInterceptor> interceptorList = new ArrayList<>();//拦截器列表,用来存储匹配处理器的拦截器
         */
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

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

                /**
                 * 2.
                 * 下面这行代码 大概做了以下事情
                 * 1.通过request和url 匹配了对应的controller以及调用的方法 填充了HandlerExecutionChain.handler
                 * 2.通过匹配request和HandlerInterceptor的注册信息(拦截哪些,放行哪些),往HandlerExecutionChain.interceptorList中添加对应的拦截器
                 */
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}
                /**
                 * 3.执行 拦截器链条中的所有前置方法
                 */
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

                /**
                 * 4.交由处理器(controller对应的方法)去处理方法中的业务逻辑
                 */
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
                
                /**
                 * 5.倒序执行 拦截器链条中的所有后置方法
                 */
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}}
        //...

流程解析#

由于Filter依赖于Servlet容器所以不同的容器Filter,FilterChain的实现类存在差异,这里以Tomcat为例分析

1.向后台发起一次请求

2.接待线程接收到,将任务转交给工作线程

3.判断协议,封装必要对象

4.将request,response一路转交至StandardWrapperValve.invoke(Request request, Response response)

5.创建过滤链ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

​ 5.1从上下文中获取注册好的过滤器

​ 5.2遍历过滤器,匹配URL,Servlet等,将匹配好的过滤器加入到过滤器链

6.依次回调FilterChain的doFilter方法filterChain.doFilter(request.getRequest(),response.getResponse());

7.将所有过滤器的前置代码执行完毕,进入servlet,servlet.service(request, response);

8.进入DispatcherServlet统一调度

9.调用拦截器前置方法

10.进入controller中的对应方法,执行具体的业务逻辑

11.调用拦截器后置(数组倒序执行)

12.将所有过滤器的后置代码执行完毕(方法栈,先进后出)

13.将结果返回给请求者

注意,过滤器和拦截器实现先进后出的实现方式是不同的,过滤器基于函数回调,方法栈结构天生支持先进后出;拦截器则是直接使用循环倒序遍历

总结#

  • 过滤器和拦截器都遵循面向切面编程的思想(AOP),实现了在请求到达目标处理器(Servlet/Controller)之前或者之后插入自定义的处理逻辑
  • 使用范围不同

    过滤器实现的是javax.servlet.Filter该接口在Servlet规范中定义,依赖WEB容器

    拦截器是一个Spring组件,由Spring管理,并不依赖Tomcat容器,可以单独使用(Application,Swing)

  • 使用的场景不同

    拦截器更加接近业务系统,所以拦截器更适用于处理统一的业务逻辑,比如权限判断等

    过滤器通常用来实现通用功能,比如xss过滤,敏感词,处理跨域等等

  • 触发的时机不同

    过滤器的触发时机早于拦截器

  • 底层实现细节不同

    过滤器实现先进后出基于方法栈的数据结构

    拦截器实现先进后出基于循环倒序遍历


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK