⭐表示重要。
第一章:概述
1.1 拦截器和过滤器解决类似的问题
- ① 生活中坐地铁的场景:为了提高乘车效率,在乘客进入站台前统一检票:

- ② 程序中:在程序中,使用拦截器在请求到达具体的 handler 方法前,统一执行检测。

1.2 拦截器 VS 过滤器
1.2.1 相同点
- ① 拦截:必须先将请求拦截,才能执行后续操作。
- ② 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理。
- ③ 放行:对请求执行了必要操作后,放请求过去,让其访问它原先要访问的资源。
1.2.2 不同点
- ① 工作平台不同:
- 过滤器工作在 Servlet 容器中。
- 拦截器工作在 SpringMVC 的基础上。
- ② 拦截的范围:
- 过滤器:能够拦截到的最大范围是整个 WEB 应用。
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求。
- ③ IOC 容器的支持:
- 过滤器:要得到 IOC 容器就需要调用专门的工具方法,是间接的。
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持。
1.3 选择
- 如果能使用 SpringMVC 的拦截器解决的功能,就不需要使用过滤器。
第二章:准备工作
2.1 环境准备
- IDEA 2021+。
- Maven 3.8。
- JDK 11+。
2.2 导入依赖
- pom.xml
<!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.12</version></dependency><!-- 日志 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.6</version></dependency><!-- ServletAPI --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope></dependency><!-- Spring5和Thymeleaf整合包 --><dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId><version>3.0.12.RELEASE</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.0</version></dependency>
2.3 日志配置文件
- logback.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug="true"><!-- 指定日志输出的位置 --><appender name="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 日志输出的格式 --><!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --><pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern><charset>UTF-8</charset></encoder></appender><!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --><!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --><root level="INFO"><!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --><appender-ref ref="STDOUT" /></root><!-- 根据特殊需求指定局部日志级别 --><logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" /></configuration>
2.4 配置web.xml
- web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://xmlns.jcp.org/xml/ns/javaee"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 配置过滤器解决 POST 请求的字符乱码问题 --><filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><!-- encoding参数指定要使用的字符集名称 --><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><!-- 请求强制编码 --><init-param><param-name>forceRequestEncoding</param-name><param-value>true</param-value></init-param><!-- 响应强制编码 --><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>CharacterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 配置HiddenHttpMethodFilter --><filter><filter-name>hiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class></filter><filter-mapping><filter-name>hiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 配置SpringMVC中负责处理请求的核心Servlet,也被称为SpringMVC的前端控制器 --><servlet><servlet-name>dispatcherServlet</servlet-name><!-- DispatcherServlet的全类名 --><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 通过初始化参数指定SpringMVC配置文件位置 --><init-param><!-- 如果不记得contextConfigLocation配置项的名称,可以到DispatcherServlet的父类FrameworkServlet中查找 --><param-name>contextConfigLocation</param-name><!-- 使用classpath:说明这个路径从类路径的根目录开始才查找 --><param-value>classpath:springmvc.xml</param-value></init-param><!-- 作为框架的核心组件,在启动过程中有大量的初始化操作要做,这些操作放在第一次请求时才执行非常不恰当 --><!-- 我们应该将DispatcherServlet设置为随Web应用一起启动 --><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><!-- 对DispatcherServlet来说,url-pattern有两种方式配置 --><!-- 方式一:配置“/”,表示匹配整个Web应用范围内所有请求。这里有一个硬性规定:不能写成“/*”。只有这一个地方有这个特殊要求,以后我们再配置Filter还是可以正常写“/*”。 --><!-- 方式二:配置“*.扩展名”,表示匹配整个Web应用范围内部分请求 --><url-pattern>/</url-pattern></servlet-mapping></web-app>
2.5 SpringMVC的配置文件
- springmvc.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"><!-- 自动扫描包 --><context:component-scan base-package="com.github.fairy.era.mvc.handler" use-default-filters="false"><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan><!-- 配置视图解析器 --><bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"><property name="order" value="1"/><property name="characterEncoding" value="UTF-8"/><property name="templateEngine"><bean class="org.thymeleaf.spring5.SpringTemplateEngine"><property name="templateResolver"><bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"><!-- 物理视图:视图前缀+逻辑视图+视图后缀 --><!-- 视图前缀 --><property name="prefix" value="/WEB-INF/templates/"/><!-- 视图后缀 --><property name="suffix" value=".html"/><property name="templateMode" value="HTML5"/><property name="characterEncoding" value="UTF-8"/></bean></property></bean></property></bean><mvc:annotation-driven/><mvc:default-servlet-handler/><mvc:view-controller path="/" view-name="portal"/></beans>
2.6 前端代码
- portal.html
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>首页</title></head><body><a th:href="@{/common/request/one}">普通请求</a></body></html>
2.6 Handler 类
- DemoHandler.java
package com.github.fairy.era.mvc.handler;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;/*** @author 许大仙* @version 1.0* @since 2021-11-11 14:58*/@Controllerpublic class DemoHandler {private final Logger logger = LoggerFactory.getLogger(this.getClass());@RequestMapping("/common/request/one")public String one() {logger.info("DemoHandler.one");return "target";}}
第三章:拦截器的创建和注册(⭐)
3.1 拦截器的创建
3.1.1 实现接口
实现 HandlerInterceptor 接口。
示例:
package com.github.fairy.era.mvc.interceptor;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/*** @author 许大仙* @version 1.0* @since 2021-11-16 09:02*/public class Process01Interceptor implements HandlerInterceptor {private final Logger logger = LoggerFactory.getLogger(this.getClass());// 在处理请求的目标 handler 方法前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {logger.info("Process01Interceptor preHandle 方法");return true;}// 在目标 handler 方法之后,渲染视图之前@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {logger.info("Process01Interceptor postHandle 方法");}// 渲染视图之后@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {logger.info("Process01Interceptor afterCompletion 方法");}}
- 单个拦截器的执行顺序:
- ① preHandle() 方法。
- ② 目标 handler() 方法。
- ③ postHandle() 方法。
- ④ 渲染视图。
- ⑤ afterCompletion() 方法。
3.1.2 继承类
- 在较低版本的 SpringMVC 中,实现 HandlerInterceptor 接口需要把所有抽象方法都实现。但是又不是每个方法都需要使用,导致代码比较繁琐。
- 此时可以通过继承 HandlerInterceptorAdapter 类同样可以创建拦截器类。HandlerInterceptorAdapter 类中已经给 HandlerInterceptor 接口提供了默认实现,我们继承后不需要把每个方法都实现,只需要把有用的方法重写即可。
- 在 SpringMVC 较高版本(例如:5.3版本以上)中,HandlerInterceptor 接口已经借助 JDK 1.8 新特性让每个抽象方法都给出了默认实现,所以 HandlerInterceptorAdapter 这个类被标记为过时。
package org.springframework.web.servlet.handler;import org.springframework.web.servlet.AsyncHandlerInterceptor;import org.springframework.web.servlet.HandlerInterceptor;/*** Abstract adapter class for the {@link AsyncHandlerInterceptor} interface,* for simplified implementation of pre-only/post-only interceptors.** @author Juergen Hoeller* @since 05.12.2003* @deprecated as of 5.3 in favor of implementing {@link HandlerInterceptor}* and/or {@link AsyncHandlerInterceptor} directly.*/@Deprecatedpublic abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {}
3.2 拦截器的注册
3.2.1 默认拦截全部请求
- springmvc.xml
<!-- 注册拦截器 --><mvc:interceptors><!-- 直接通过内部 bean 配置的拦截器默认拦截全部请求(SpringMVC 范围内) --><bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean></mvc:interceptors>
3.2.2 配置拦截路径之精确匹配
- springmvc.xml
<!-- 注册拦截器 --><mvc:interceptors><!-- 具体配置拦截器可以指定拦截的请求地址 --><mvc:interceptor><!-- 精确匹配 --><mvc:mapping path="/common/request/one"/><bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean></mvc:interceptor></mvc:interceptors>
3.2.3 配置拦截路径之模糊匹配:匹配单层路径
- springmvc.xml
<!-- 注册拦截器 --><mvc:interceptors><!-- 具体配置拦截器可以指定拦截的请求地址 --><mvc:interceptor><!-- 模糊匹配 --><mvc:mapping path="/common/request/*"/><bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean></mvc:interceptor></mvc:interceptors>
3.2.4 配置拦截路径之模糊匹配:匹配多层路径
- springmvc.xml
<!-- 注册拦截器 -->
<mvc:interceptors>
<!-- 具体配置拦截器可以指定拦截的请求地址 -->
<mvc:interceptor>
<!-- 模糊匹配 -->
<mvc:mapping path="/common/**"/>
<bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
3.2.5 配置不拦截路径
- springmvc.xml
<!-- 注册拦截器 -->
<mvc:interceptors>
<!-- 具体配置拦截器可以指定拦截的请求地址 -->
<mvc:interceptor>
<!-- /**匹配路径中的多层 -->
<mvc:mapping path="/common/request/*"/>
<!-- 使用 mvc:exclude-mapping 标签配置不拦截的地址 -->
<mvc:exclude-mapping path="/common/request/two/bbb"/>
<bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
3.3 多个拦截器执行顺序
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照
配置顺序调用各个 preHandle() 方法。 - 目标 handler 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照
配置相反的顺序调用各个 postHandle() 方法。 - 渲染视图。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照
配置相反的顺序调用各个 afterCompletion() 方法。
