过滤器和拦截器的辨析

过滤器和拦截器的辨析

介绍

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

  • 过滤器:

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

  • 拦截器

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

原理解析

过滤器

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

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

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

FilterChain就是一个接口

java 复制代码
public interface FilterChain {

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

}

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

java 复制代码
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()方法

java 复制代码
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过滤,敏感词,处理跨域等等

  • 触发的时机不同

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

  • 底层实现细节不同

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

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

相关推荐
Flittly1 天前
【SpringSecurity新手村系列】(3)自定义登录页与表单认证
java·笔记·安全·spring·springboot
Flittly2 天前
【SpringSecurity新手村系列】(2)整合 MyBatis 实现数据库认证
java·安全·spring·springboot·安全架构
极光代码工作室2 天前
基于SpringBoot的在线考试系统
java·springboot·web开发·后端开发
YDS8293 天前
大营销平台 —— 抽奖规则决策树
java·springboot·ddd
码农张34 天前
自定义跨字段校验必填注解
springboot
格鸰爱童话4 天前
向AI学习项目技能(七)
学习·springboot
代码漫谈4 天前
微服务 vs 单体架构:架构选型、实战拆解与决策指南
java·微服务·springboot·springcloud
文慧的科技江湖4 天前
光储充一体化系统落地 PRD 全功能清单 - 慧知开源充电桩平台
java·mysql·开源·springboot·慧知开源充电桩平台·充电重复订单解决方案源码
Flittly5 天前
【SpringAIAlibaba新手村系列】(16)调用百度 MCP 服务
java·笔记·spring·ai·springboot
MegaDataFlowers6 天前
yaml配置注入
springboot