1. Spring MVC 中的拦截器的使用“拦截器基本配置” 和 “拦截器高级配置”

@


2. 拦截器

拦截器(Interceptor) 类似于过滤器(Filter)

Spring MVC 的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。

拦截器可以用于很多场景下:

  1. 登录验证:对于需要登录才能访问的地址,使用拦截器可以判断用户是否已登录,如果未登录,则跳转到登录页面。
  2. 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问。
  3. 请求日志:记录请求信息,例如:请求地址,请求参数,请求时间等,用于排查问题和性能优化。
  4. 更改响应:可以对响应的内容进行修改,例如:添加头信息,调整响应内容格式等。

拦截器和过滤器的区别在于它们的作用层面不同:

  • 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如:设置编码和字符集,请求头,状态码等。
  • 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如:打印日志,权限验证等。

Filter、Servlet、Interceptor、Controller的执行顺序:

在这里插入图片描述

3. Spring MVC 中的拦截器的创建和基本配置

3.1 定义拦截

实现
org.springframework.web.servlet.HandlerInterceptor
接口,共有三个方法可以进行选择性的实现:

在这里插入图片描述

  • preHandle( ):处理器方法调用之前执行。
    只有该方法有返回值,返回值是布尔类型,true 表示放行,false 表示拦截
  • postHandle( ):处理器方法调用之后执行。
  • afterCompletion( ):渲染完成后执行。

3.2 拦截器基本配置

第一步:编写拦截器,该拦截器要实现
org.springframework.web.servlet.HandlerInterceptor
接口

在这里插入图片描述

package com.rainbowsea.springmvc.interceptors;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;



public class Interceptor1 implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor1's preHandle!");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Interceptor1's postHandle!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor1's afterCompletion!");
    }
}

在 Spring MVC 中拦截器的基本配置有两种方式:

  • 第一种方式是:通过 xml 进行配置
  • 第二种方式是:通过 @Component 注解 + xml 文件进行配置

第一种方式:通过 xml 进行配置

需要注意的是:这个基本配置,默认情况下是拦截所有请求的。

在 springmvc.xml 文件中进行如下配置:

<mvc:interceptors>
    <bean class="com.powernode.springmvc.interceptors.Interceptor1"/>
</mvc:interceptors>

在这里插入图片描述

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--    组件扫描-->
    <context:component-scan
            base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan>

    <!--    视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--    配置拦截器-->
        <mvc:interceptors>
    <!--        基本配置,第一种方式
            注意:基本配置,默认情况下是拦截所有请求的-->
            <bean class="com.rainbowsea.springmvc.interceptors.Interceptor1"></bean>
        </mvc:interceptors>
</beans>

编写对应的 Controller 控制器进行测试:

在这里插入图片描述

package com.rainbowsea.springmvc.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller  // 交给 Spring IOC 容器管理
public class IndexController {


    @RequestMapping("/index")
    public String toIndex() {
        System.out.println("IndexController#toIndex()  ---> 处理器方法执行了");
        return "index";
    }


    @RequestMapping("ok")
    public String toOK() {
        System.out.println("IndexController#OK() ---> 处理器方法执行了");
        return "ok";
    }
}

运行测试:

在这里插入图片描述

在这里插入图片描述

第二种方式是:通过 @Component 注解 + xml 文件进行配置

注意:同样的,
对于这种基本配置来说,拦截器是拦截所有请求的。

第二种方式的前提:

  1. 前提1:包扫描,在 spring mvc 中配置组件扫描

在这里插入图片描述

  1. 前提2:使用 @Component 注解进行对 编写的拦截器类进行标注即可。
    在这里插入图片描述
  1. 两个前提都搞定了,就可以在 spring mvc.xml 文件中进行配置了。
  2. 在这里插入图片描述
<mvc:interceptors>
    <ref bean="interceptor1"/>
</mvc:interceptors>

运行测试:

在这里插入图片描述

3.3 拦截器的高级配置

采用以上基本配置方式,拦截器是拦截所有请求路径的。如果要针对某些路径进行拦截,某些路径不拦截,某些路径拦截,可以采用高级配置:在 spring mvc.xml 文件当中进行配置

在这里插入图片描述

以上的配置表示,除 /ok 请求路径之外,剩下的路径全部拦截。

<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--    组件扫描-->
    <context:component-scan
            base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan>

    <!--    视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--    高级配置:指定一些路径被拦截,一些路径不拦截-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--            /** 表示拦截所有路径-->
            <mvc:mapping path="/**"/>
            <!--            /ok 请求路径不拦截-->
            <mvc:exclude-mapping path="/ok"/>
            <!--            /index 请求路径拦截-->
            <!--            <mvc:mapping path="/index"/>-->
            <!--            设置对应的那个拦截器-->
            <ref bean="interceptor1"></ref>
        </mvc:interceptor>

    </mvc:interceptors>
</beans>

运行测试:

在这里插入图片描述

4. Spring MVC中多个拦截器的执行顺序

这里我们为了探究,多个拦截器存在的时候的执行顺序,我们创建 3 个 拦截器。如下:

4.1 如果所有拦截器 preHandle( ) 方法 都返回 true时,多个拦截器的的执行顺序

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

配置多个拦截器

在这里插入图片描述

    <mvc:interceptors>

        <!--        配置多个拦截器,这个是基本配置,默认是所有请求都会进行拦截处理-->
        <ref bean="interceptor1"></ref>
        <ref bean="interceptor2"></ref>
        <ref bean="interceptor3"></ref>
    </mvc:interceptors>
<?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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--    组件扫描-->
    <context:component-scan
            base-package="com.rainbowsea.springmvc.controller,com.rainbowsea.springmvc.interceptors"></context:component-scan>

    <!--    视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <mvc:interceptors>

        <!--        配置多个拦截器,这个是基本配置,默认是所有请求都会进行拦截处理-->
        <ref bean="interceptor1"></ref>
        <ref bean="interceptor2"></ref>
        <ref bean="interceptor3"></ref>
    </mvc:interceptors>
</beans>

如果所有拦截器 preHandle 都返回 true

在这里插入图片描述

按照 springmvc.xml文件中配置的顺序,自上而下调用 preHandle:

在这里插入图片描述

在这里插入图片描述

4.2 如果其中一个拦截器 preHandle ( ) 方法,返回 false,多个拦截器的的执行顺序

Interceptor3 拦截器中的 preHandle()方法返回 false。其他两个拦截器返回 true.

在这里插入图片描述

规则:只要有一个拦截器
preHandle
返回false,任何
postHandle
都不执行。但返回false的拦截器的前面的拦截器按照逆序执行
afterCompletion

在这里插入图片描述

  1. 只要有一个拦截器
    preHandle()
    方法,返回false,则任何拦截器的
    postHandle()方法
    都不执行。但返回 false 的拦截器的前面的拦截器按照逆序执行
    afterCompletion

  2. 返回 false 拦截器,拦截住了,则其中的 Controllor控制器不执行了,其中的 postHandle

    一个也不会执行。而对应的 afterCompletion()方法,的执行是按照配置拦截器(自上而下)的倒序执行,但其中返回 false 的拦截器中的 afterCompletion()方法不会被执行

  3. 只要有一个拦截器
    preHandle
    返回false,任何
    postHandle
    都不执行。但返回false的拦截器的前面的拦截器按照逆序执行
    afterCompletion
    。只要有一个拦截器
    preHandle
    返回false,任何
    postHandle
    都不执行。但返回false的拦截器的前面的拦截器按照逆序执行
    afterCompletion

5. 补充:源码分析

5.1 方法执行顺序的源码分析

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 调用所有拦截器的 preHandle 方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // 调用处理器方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 调用所有拦截器的 postHandle 方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        // 处理视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
        // 渲染页面
        render(mv, request, response);
        // 调用所有拦截器的 afterCompletion 方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

5.2 拦截与放行的源码分析

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 调用所有拦截器的 preHandle 方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            // 如果 mappedHandler.applyPreHandle(processedRequest, response) 返回false,以下的return语句就会执行
            return;
        }
    }
}
public class HandlerExecutionChain {
    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)) {
				triggerAfterCompletion(request, response, null);
                // 如果 interceptor.preHandle(request, response, this.handler) 返回 false,以下的 return false;就会执行。
				return false;
			}
			this.interceptorIndex = i;
		}
		return true;
	}
}

5.3 DispatcherServlet 和 HandlerExecutionChain 的部分源码:

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 按照顺序执行所有拦截器的preHandle方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // 执行处理器方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 按照逆序执行所有拦截器的 postHanle 方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        // 处理视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
        // 渲染视图
        render(mv, request, response);
        // 按照逆序执行所有拦截器的 afterCompletion 方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
public class HandlerExecutionChain {
    // 顺序执行 preHandle
    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)) {
                // 如果其中一个拦截器preHandle返回false
                // 将该拦截器前面的拦截器按照逆序执行所有的afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
        return true;
	}
    // 逆序执行 postHanle
    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);
        }
	}
    // 逆序执行 afterCompletion
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
		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);
			}
		}
	}
}

6. 总结:

  1. 实现
    org.springframework.web.servlet.HandlerInterceptor
    接口,共有三个方法可以进行选择性的实现:


    • preHandle( ):处理器方法调用之前执行。
      只有该方法有返回值,返回值是布尔类型,true 表示放行,false 表示拦截
    • postHandle( ):处理器方法调用之后执行。
    • afterCompletion( ):渲染完成后执行。
  2. 在 Spring MVC 中拦截器的基本配置有两种方式:


    • 第一种方式是:通过 xml 进行配置
    • 第二种方式是:通过 @Component 注解 + xml 文件进行配置
    • 对于这种基本配置来说,拦截器是拦截所有请求的。
  3. 拦截器的高级配置:采用以上基本配置方式,拦截器是拦截所有请求路径的。如果要针对某些路径进行拦截,某些路径不拦截,某些路径拦截,可以采用高级配置:在 spring mvc.xml 文件当中进行配置

  4. Spring MVC中多个拦截器的执行顺序:


    1. 如果所有拦截器 preHandle( ) 方法 都返回 true时,多个拦截器的的执行顺序:


      1. 按照 springmvc.xml文件中配置的顺序,自上而下调用 preHandle:

        在这里插入图片描述

  1. 如果其中一个拦截器 preHandle ( ) 方法,返回 false,多个拦截器的的执行顺序


    1. 只要有一个拦截器
      preHandle()
      方法,返回false,则任何拦截器的
      postHandle()方法
      都不执行。但返回 false 的拦截器的前面的拦截器按照逆序执行
      afterCompletion
  2. 拦截器源码分析。

7. 最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述

标签: none

添加新评论