⭐表示重要。

第一章:概述

1.1 拦截器和过滤器解决类似的问题

  • ① 生活中坐地铁的场景:为了提高乘车效率,在乘客进入站台前统一检票:

生活中坐地铁的场景.png

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

程序中使用拦截器的场景.png

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
  1. <!-- SpringMVC -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-webmvc</artifactId>
  5. <version>5.3.12</version>
  6. </dependency>
  7. <!-- 日志 -->
  8. <dependency>
  9. <groupId>ch.qos.logback</groupId>
  10. <artifactId>logback-classic</artifactId>
  11. <version>1.2.6</version>
  12. </dependency>
  13. <!-- ServletAPI -->
  14. <dependency>
  15. <groupId>javax.servlet</groupId>
  16. <artifactId>javax.servlet-api</artifactId>
  17. <version>4.0.1</version>
  18. <scope>provided</scope>
  19. </dependency>
  20. <!-- Spring5和Thymeleaf整合包 -->
  21. <dependency>
  22. <groupId>org.thymeleaf</groupId>
  23. <artifactId>thymeleaf-spring5</artifactId>
  24. <version>3.0.12.RELEASE</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>com.fasterxml.jackson.core</groupId>
  28. <artifactId>jackson-databind</artifactId>
  29. <version>2.13.0</version>
  30. </dependency>

2.3 日志配置文件

  • logback.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration debug="true">
  3. <!-- 指定日志输出的位置 -->
  4. <appender name="STDOUT"
  5. class="ch.qos.logback.core.ConsoleAppender">
  6. <encoder>
  7. <!-- 日志输出的格式 -->
  8. <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
  9. <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
  10. <charset>UTF-8</charset>
  11. </encoder>
  12. </appender>
  13. <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
  14. <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
  15. <root level="INFO">
  16. <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
  17. <appender-ref ref="STDOUT" />
  18. </root>
  19. <!-- 根据特殊需求指定局部日志级别 -->
  20. <logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG" />
  21. </configuration>

2.4 配置web.xml

  • web.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  5. http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  6. version="4.0">
  7. <!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
  8. <filter>
  9. <filter-name>CharacterEncodingFilter</filter-name>
  10. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  11. <!-- encoding参数指定要使用的字符集名称 -->
  12. <init-param>
  13. <param-name>encoding</param-name>
  14. <param-value>UTF-8</param-value>
  15. </init-param>
  16. <!-- 请求强制编码 -->
  17. <init-param>
  18. <param-name>forceRequestEncoding</param-name>
  19. <param-value>true</param-value>
  20. </init-param>
  21. <!-- 响应强制编码 -->
  22. <init-param>
  23. <param-name>forceResponseEncoding</param-name>
  24. <param-value>true</param-value>
  25. </init-param>
  26. </filter>
  27. <filter-mapping>
  28. <filter-name>CharacterEncodingFilter</filter-name>
  29. <url-pattern>/*</url-pattern>
  30. </filter-mapping>
  31. <!-- 配置HiddenHttpMethodFilter -->
  32. <filter>
  33. <filter-name>hiddenHttpMethodFilter</filter-name>
  34. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  35. </filter>
  36. <filter-mapping>
  37. <filter-name>hiddenHttpMethodFilter</filter-name>
  38. <url-pattern>/*</url-pattern>
  39. </filter-mapping>
  40. <!-- 配置SpringMVC中负责处理请求的核心Servlet,也被称为SpringMVC的前端控制器 -->
  41. <servlet>
  42. <servlet-name>dispatcherServlet</servlet-name>
  43. <!-- DispatcherServlet的全类名 -->
  44. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  45. <!-- 通过初始化参数指定SpringMVC配置文件位置 -->
  46. <init-param>
  47. <!-- 如果不记得contextConfigLocation配置项的名称,可以到DispatcherServlet的父类FrameworkServlet中查找 -->
  48. <param-name>contextConfigLocation</param-name>
  49. <!-- 使用classpath:说明这个路径从类路径的根目录开始才查找 -->
  50. <param-value>classpath:springmvc.xml</param-value>
  51. </init-param>
  52. <!-- 作为框架的核心组件,在启动过程中有大量的初始化操作要做,这些操作放在第一次请求时才执行非常不恰当 -->
  53. <!-- 我们应该将DispatcherServlet设置为随Web应用一起启动 -->
  54. <load-on-startup>1</load-on-startup>
  55. </servlet>
  56. <servlet-mapping>
  57. <servlet-name>dispatcherServlet</servlet-name>
  58. <!-- 对DispatcherServlet来说,url-pattern有两种方式配置 -->
  59. <!-- 方式一:配置“/”,表示匹配整个Web应用范围内所有请求。这里有一个硬性规定:不能写成“/*”。只有这一个地方有这个特殊要求,以后我们再配置Filter还是可以正常写“/*”。 -->
  60. <!-- 方式二:配置“*.扩展名”,表示匹配整个Web应用范围内部分请求 -->
  61. <url-pattern>/</url-pattern>
  62. </servlet-mapping>
  63. </web-app>

2.5 SpringMVC的配置文件

  • springmvc.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns="http://www.springframework.org/schema/beans"
  5. 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">
  6. <!-- 自动扫描包 -->
  7. <context:component-scan base-package="com.github.fairy.era.mvc.handler" use-default-filters="false">
  8. <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  9. </context:component-scan>
  10. <!-- 配置视图解析器 -->
  11. <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
  12. <property name="order" value="1"/>
  13. <property name="characterEncoding" value="UTF-8"/>
  14. <property name="templateEngine">
  15. <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
  16. <property name="templateResolver">
  17. <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
  18. <!-- 物理视图:视图前缀+逻辑视图+视图后缀 -->
  19. <!-- 视图前缀 -->
  20. <property name="prefix" value="/WEB-INF/templates/"/>
  21. <!-- 视图后缀 -->
  22. <property name="suffix" value=".html"/>
  23. <property name="templateMode" value="HTML5"/>
  24. <property name="characterEncoding" value="UTF-8"/>
  25. </bean>
  26. </property>
  27. </bean>
  28. </property>
  29. </bean>
  30. <mvc:annotation-driven/>
  31. <mvc:default-servlet-handler/>
  32. <mvc:view-controller path="/" view-name="portal"/>
  33. </beans>

2.6 前端代码

  • portal.html
  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>首页</title>
  6. </head>
  7. <body>
  8. <a th:href="@{/common/request/one}">普通请求</a>
  9. </body>
  10. </html>

2.6 Handler 类

  • DemoHandler.java
  1. package com.github.fairy.era.mvc.handler;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. /**
  7. * @author 许大仙
  8. * @version 1.0
  9. * @since 2021-11-11 14:58
  10. */
  11. @Controller
  12. public class DemoHandler {
  13. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  14. @RequestMapping("/common/request/one")
  15. public String one() {
  16. logger.info("DemoHandler.one");
  17. return "target";
  18. }
  19. }

第三章:拦截器的创建和注册(⭐)

3.1 拦截器的创建

3.1.1 实现接口

  • 实现 HandlerInterceptor 接口。

  • 示例:

  1. package com.github.fairy.era.mvc.interceptor;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.web.servlet.HandlerInterceptor;
  5. import org.springframework.web.servlet.ModelAndView;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. /**
  9. * @author 许大仙
  10. * @version 1.0
  11. * @since 2021-11-16 09:02
  12. */
  13. public class Process01Interceptor implements HandlerInterceptor {
  14. private final Logger logger = LoggerFactory.getLogger(this.getClass());
  15. // 在处理请求的目标 handler 方法前执行
  16. @Override
  17. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  18. logger.info("Process01Interceptor preHandle 方法");
  19. return true;
  20. }
  21. // 在目标 handler 方法之后,渲染视图之前
  22. @Override
  23. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  24. logger.info("Process01Interceptor postHandle 方法");
  25. }
  26. // 渲染视图之后
  27. @Override
  28. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  29. logger.info("Process01Interceptor afterCompletion 方法");
  30. }
  31. }
  • 单个拦截器的执行顺序:
  • ① preHandle() 方法。
  • ② 目标 handler() 方法。
  • ③ postHandle() 方法。
  • ④ 渲染视图。
  • ⑤ afterCompletion() 方法。

3.1.2 继承类

  • 在较低版本的 SpringMVC 中,实现 HandlerInterceptor 接口需要把所有抽象方法都实现。但是又不是每个方法都需要使用,导致代码比较繁琐。
  • 此时可以通过继承 HandlerInterceptorAdapter 类同样可以创建拦截器类。HandlerInterceptorAdapter 类中已经给 HandlerInterceptor 接口提供了默认实现,我们继承后不需要把每个方法都实现,只需要把有用的方法重写即可。
  • 在 SpringMVC 较高版本(例如:5.3版本以上)中,HandlerInterceptor 接口已经借助 JDK 1.8 新特性让每个抽象方法都给出了默认实现,所以 HandlerInterceptorAdapter 这个类被标记为过时。
  1. package org.springframework.web.servlet.handler;
  2. import org.springframework.web.servlet.AsyncHandlerInterceptor;
  3. import org.springframework.web.servlet.HandlerInterceptor;
  4. /**
  5. * Abstract adapter class for the {@link AsyncHandlerInterceptor} interface,
  6. * for simplified implementation of pre-only/post-only interceptors.
  7. *
  8. * @author Juergen Hoeller
  9. * @since 05.12.2003
  10. * @deprecated as of 5.3 in favor of implementing {@link HandlerInterceptor}
  11. * and/or {@link AsyncHandlerInterceptor} directly.
  12. */
  13. @Deprecated
  14. public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
  15. }

3.2 拦截器的注册

3.2.1 默认拦截全部请求

  • springmvc.xml
  1. <!-- 注册拦截器 -->
  2. <mvc:interceptors>
  3. <!-- 直接通过内部 bean 配置的拦截器默认拦截全部请求(SpringMVC 范围内) -->
  4. <bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean>
  5. </mvc:interceptors>

3.2.2 配置拦截路径之精确匹配

  • springmvc.xml
  1. <!-- 注册拦截器 -->
  2. <mvc:interceptors>
  3. <!-- 具体配置拦截器可以指定拦截的请求地址 -->
  4. <mvc:interceptor>
  5. <!-- 精确匹配 -->
  6. <mvc:mapping path="/common/request/one"/>
  7. <bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean>
  8. </mvc:interceptor>
  9. </mvc:interceptors>

3.2.3 配置拦截路径之模糊匹配:匹配单层路径

  • springmvc.xml
  1. <!-- 注册拦截器 -->
  2. <mvc:interceptors>
  3. <!-- 具体配置拦截器可以指定拦截的请求地址 -->
  4. <mvc:interceptor>
  5. <!-- 模糊匹配 -->
  6. <mvc:mapping path="/common/request/*"/>
  7. <bean class="com.github.fairy.era.mvc.interceptor.Process01Interceptor"></bean>
  8. </mvc:interceptor>
  9. </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() 方法。