spring揭秘25-springmvc05-过滤器与拦截器区别(补充)

文章目录

【README】

代码详情参见: springmvcDiscoverFirstDemo【github】

1)本文根据spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理) 中的拦截器与过滤器进行总结;

  • 确切的说本文讨论的拦截器指的是springmvc拦截器,过滤器是servlet过滤器;springmvc是基于servlet构建的web框架,但两者有区别;(当然了,servlet有拦截器,也有过滤器)

2)拦截器与过滤器都可以对业务逻辑做拦截,即在上下文织入逻辑,所以把两者放在一起比较;

  • 拦截器与过滤器最重要的应用场景:本文认为是偷梁换柱, 在拦截方法中,用新对象替换已有对象 ; (当然也有其他场景,如日志收集, 参数校验)

【1】springmvc拦截器回顾

【1.1】定义与应用

1)springmvc拦截器定义:基于servlet拦截器思想,springmvc提供的对servlet内部处理逻辑进行拦截的抽象接口;

2)应用场景:日志收集, 参数校验等; 其实最重要的应用,应该是偷梁换柱;就是在拦截方法中,可以用新对象替换已有对象 ;

【HandlerInterceptor】

java 复制代码
package org.springframework.web.servlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;

public interface HandlerInterceptor {
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}
}

【TimeCostHandlerInterceptor】自定义拦截器

java 复制代码
public class TimeCostHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("startTime" , System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        Long startTime = (Long) request.getAttribute("startTime");
        System.out.println(request.getServletPath() + " 执行耗时统计(单位毫秒)=" + (System.currentTimeMillis() - startTime));
    }
}

【dispatcher-servlet.xml】装配拦截器到HandlerMapping

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 注册自定义处理器拦截器 -->
    <bean id="timeCostHandlerInterceptor"  class="com.tom.springmvc.handlerinterceptor.TimeCostHandlerInterceptor"/>

    <!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
    <bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="timeCostHandlerInterceptor" /> <!-- 装配拦截器 --> 
            </list>
        </property>
    </bean>

    <!-- SimpleUrlHandlerMapping: 可以配置web请求到具体二级控制器的映射, 可以把一组或多组拥有相似特征的web请求映射给二级控制器-->
    <bean id="handlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <!-- 设置SimpleUrlHandlerMapping优先级为1,优先匹配,若有多个HandlerMapping时 -->
        <property name="order" value="1" />
        <property name="mappings">
            <value>
                /userController.do=userController
                /bankCard*.do=bankCardController
                /bankCard/*.do=bankCardController
                /pdfUrlView*.do=pdfUrlViewController
            </value>
        </property>
    </bean>

</beans>

【1.2】拦截器作用范围

1) 由配置可知,拦截器的作用范围是HandlerMapping ; 如本文配置了2个HandlerMapping,包括 BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping ;而只有BeanNameUrlHandlerMapping装配了timeCostHandlerInterceptor拦截器,而SimpleUrlHandlerMapping 没有;

  • 所以:通过BeanNameUrlHandlerMapping找到的二级处理器,并调用该处理器时,才会有timeCostHandlerInterceptor拦截功能;而SimpleUrlHandlerMapping 没有;
  • 而二级处理器是由一级处理器DispatcherServlet调用HandlerMapping查找到的,所以总结起来,springmvc拦截器是在Servlet内部做拦截;(补充:servlet拦击器是对servlet外部做拦截,即对servlet上下文做拦截) ;

【2】servlet过滤器回顾

【2.1】过滤器定义与应用

1)servlet过滤器:servlet框架提供的对servlet逻辑做前置过滤的抽象接口;

2)应用场景:如参数校验; 与拦截器类似, 其实最重要的应用,应该是偷梁换柱;就是在拦截方法(doFilter)中,可以用新对象替换已有对象 ;

java 复制代码
package jakarta.servlet;

import java.io.IOException;

public interface Filter {

    default public void init(FilterConfig filterConfig) throws ServletException {
    }

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

    default public void destroy() {
    }
}

【CustomFilter】自定义过滤器

java 复制代码
public class CustomFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(request.getServletContext().getContextPath() + " CustomFilter 过滤器执行");
        chain.doFilter(request, response);
        System.out.println(request.getServletContext().getContextPath() + " CustomFilter 过滤器执行完成后");
    }
}

【web.xml】注册过滤器代理到servlet容器

xml 复制代码
<!-- 注册过滤器代理 -->
<filter>
  <filter-name>customFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>customFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

【applicationContext.xml】自定义过滤器注册到spring容器

xml 复制代码
<!-- 注册自定义过滤器 -->
<bean id="customFilter"  class="com.tom.springmvc.filter.CustomFilter"/>

【2.2】过滤器作用范围

1)过滤器: 对servlet进行拦截,且仅可以做前置拦截(在servlet入口方法执行前拦截); 若满足条件,则调用FilterChain.doFilter()放行;否则封装响应报文,直接返回;


【3】springmvc拦截器与servlet过滤器区别(重要*)

1)拦截位置不同:

  • 拦截器有3个拦截方法,包括preHandle, postHandle, afterCompletion 方法;可以在上文与下文以及结束时拦截;
  • 过滤器只有1个拦截方法, 包括doFilter方法; 也可以在上下文拦截(FilterChain.doFilter()的上文和下文织入业务逻辑);

2)放行请求的方式不同:

  • 拦截器的preHandle方法通过返回true/false放行请求;
  • 过滤器的doFilter()) 通过调用 FilterChain.doFilter() 放行请求; (责任链模式)

3)作用范围不同:

  • 拦截器是装配给HandlerMapping,是对servlet内部逻辑进行拦截;(本文特指springmvc拦截器)
  • 过滤器是对servlet上文进行拦截,是对servlet外部逻辑进行拦截;(本文特指servlet过滤器)

4)底层拦截机制不同:

  • 拦截器:若有多个拦截器,只要拦截器的preHandle执行通过(返回true,这个非常重要),则其afterCompletion 一定执行(无论是否抛出异常);类似于双向链表;
    • 参考【3.1】HandlerExecutionChain#applyPreHandle()代码, 只有preHandle返回true的拦截器,才会执行其afterCompletion 方法;
  • 过滤器:若有多个过滤器,是通过 FilterChain.doFilter() 把请求透传下去,类似于单向链表;

【3.1】拦截方法调用代码实现

【HandlerExecutionChain】 拦截方法调用

java 复制代码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
       HandlerInterceptor interceptor = this.interceptorList.get(i);
       if (!interceptor.preHandle(request, response, this.handler)) {
           // 若有一个拦击器前置拦截返回false,则已执行preHandle方法的所有拦截器的afterCompletion方法被执行 【因为暂存了已执行preHandle方法的拦截器索引或下标】 
          triggerAfterCompletion(request, response, null);
          return false;
       }
       this.interceptorIndex = i; // 暂存已执行preHandle方法的拦截器索引 (下标递增)
    }
    return true;
}

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
       throws Exception {

    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
       HandlerInterceptor interceptor = this.interceptorList.get(i);
       interceptor.postHandle(request, response, this.handler, mv);
    }
}

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    // 把已执行preHandle方法的拦截器索引取出,执行其afterCompletion方法(下标递减)
    for (int i = this.interceptorIndex; i >= 0; i--) { 
       HandlerInterceptor interceptor = this.interceptorList.get(i);
       try {
          interceptor.afterCompletion(request, response, this.handler, ex);
       }
       catch (Throwable ex2) {
          logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
       }
    }
}
相关推荐
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
刘大辉在路上1 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
FF在路上2 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人3 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.3 小时前
Mybatis-Plus
java·开发语言
不良人天码星3 小时前
lombok插件不生效
java·开发语言·intellij-idea
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云3 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台