SpringMVC拦截器最详细学习笔记

文章目录

  • 八、SpringMVC拦截器
    • 8.1 什么是拦截器
    • 8.2 拦截器的配置
      • 8.2.1 配置拦截器拦截所有的控制器方法
      • 8.2.2 配置拦截器拦截指定的控制器方法
    • 8.3 多个拦截器的执行顺序

八、SpringMVC拦截器

8.1 什么是拦截器

SpringMVC中的拦截器主要用于拦截控制器方法的执行。要想写一个自己定义的拦截器,我们需要实现HandlerInterceptor接口(推荐)或者继承HandlerInterceptorAdapter类

一个浏览器的请求发送到服务器历经的流程如下:
SpringMVC拦截器最详细学习笔记_第1张图片
直接扒源码看吧:
首先我们打开DispatcherServlet类,往下拉,拉到533行(导入的包版本不同,对应的行数可能不同),可以看到:
SpringMVC拦截器最详细学习笔记_第2张图片
再往下拉,我们可以看到:
SpringMVC拦截器最详细学习笔记_第3张图片

8.2 拦截器的配置

首先,我们新建一个Moodle,然后配置我们的pom.xml:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>mvcDemo3artifactId>
    <version>1.0-SNAPSHOTversion>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <packaging>warpackaging>

    <dependencies>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>5.3.9version>
        dependency>

        
        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>4.0.1version>
            <scope>providedscope>
        dependency>

        
        <dependency>
            <groupId>org.thymeleafgroupId>
            <artifactId>thymeleaf-spring5artifactId>
            <version>3.0.12.RELEASEversion>
        dependency>
    dependencies>

project>

然后配置我们的web.xml


<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    
    <filter>
        <filter-name>CharacterEncodingFilterfilter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
        <init-param>
            <param-name>encodingparam-name>
            <param-value>UTF-8param-value>
        init-param>
        <init-param>
            <param-name>forceResponseEncodingparam-name>
            <param-value>trueparam-value>
        init-param>
    filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilterfilter-name>
        <url-pattern>/*url-pattern>
    filter-mapping>

    
    <servlet>
        <servlet-name>SpringMVCservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>classpath:springMVC.xmlparam-value>
        init-param>
        
        <load-on-startup>1load-on-startup>
    servlet>
    <servlet-mapping>
        <servlet-name>SpringMVCservlet-name>
        
        <url-pattern>/url-pattern>
    servlet-mapping>
web-app>

再配置我们的springMVC.xml


<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/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    
    <context:component-scan base-package="com.example"/>

    
    <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:default-servlet-handler/>

    
    <mvc:annotation-driven>
        <mvc:message-converters>
            
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"/>
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/htmlvalue>
                        <value>application/jsonvalue>
                    list>
                property>
            bean>
        mvc:message-converters>
    mvc:annotation-driven>
beans>

8.2.1 配置拦截器拦截所有的控制器方法

首先写一个简单的index.html和一个控制器执行成功后跳转的success.html页面如下:
index.html

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页title>
head>
<body>
    <h1>首页h1>
    <a th:href="@{/testInterceptor}">测试拦截器a><br>
body>
html>

success.html

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>成功title>
head>
<body>
    <h1>跳转成功h1>
body>
html>

然后我们写我们的控制器类
InterceptorController.java

package com.example.controller;

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

@Controller
public class InterceptorController {

    @RequestMapping("/")
    public String toIndex(){
        return "index";
    }

    @RequestMapping("/testInterceptor")
    public ModelAndView testInterceptor(ModelAndView modelAndView){
        System.out.println("Controller方法被执行");
        modelAndView.setViewName("success");
        return modelAndView;
    }
}

再写我们的拦截器方法:
MyInterceptor.java

package com.example.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor implements HandlerInterceptor {
    /*控制器方法执行前执行*/
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor ----> preHandle");
        /*
            返回true表示放行
            返回false表示拦截,会阻止控制器方法的执行
        */
        return true;
    }

    /*控制器方法执行后执行*/
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor ----> postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /*视图渲染后执行*/
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor ----> afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

最后在springMVC.xml中配置我们的拦截器,只设置bean,则该拦截器会拦截所有的控制器方法。

    
    <mvc:interceptors>
        <bean id="myInterceptor" class="com.example.interceptor.MyInterceptor">bean>
    mvc:interceptors>

启动服务器:
我们可以看到首页启动,且后台输出:(这里首页没输出控制器方法被执行是因为我没有让他输出。toIndex这个控制器方法,我就单纯返回了index,并没有让他刻意输出什么东西)
SpringMVC拦截器最详细学习笔记_第4张图片
在这里插入图片描述
点击我们的超链接,浏览器和后台分别显示:
SpringMVC拦截器最详细学习笔记_第5张图片
SpringMVC拦截器最详细学习笔记_第6张图片

在springMVC中除了用bean标签设置我们自定义的拦截器的类,我们还可以用ref标签设置。用法如下:
首先,在我们的拦截器类上添加注解@Component
SpringMVC拦截器最详细学习笔记_第7张图片
然后,在springMVC配置文件中,修改我们的拦截器配置如下:

    
    <mvc:interceptors>
    	
        <ref bean="myInterceptor">ref>
    mvc:interceptors>

效果跟前面一毛一样,这里就不再掩饰了。

8.2.2 配置拦截器拦截指定的控制器方法

只需要在springMVC配置文件中用mvc:interceptor标签配置即可。
然后内嵌使用

标签 作用
mvc:mapping 拦截该属性中配置的控制器方法
mvc:exclude-mapping 不拦截该属性中配置的控制器方法
ref 配置我们的拦截器所对应的bean

具体配置方式如下:

    
    <mvc:interceptors>


        <mvc:interceptor>
            
            <mvc:mapping path="/*"/>
            
            <mvc:exclude-mapping path="/"/>
            
            <ref bean="myInterceptor"/>
        mvc:interceptor>
    mvc:interceptors>

配置完成后,我们重启服务器。
启动我们的首页,不再拦截。
在这里插入图片描述
然后我们点击超链接进去,成功拦截:
SpringMVC拦截器最详细学习笔记_第8张图片
这里特别需要注意的是,下面这个标签设置的是拦截所有只有一层目录的控制器方法

也就是说现在如果这个控制器方法的路径是 工程路径/testInterceptor 的话,那么会被拦截。但是如果是 工程路径/hello/testInterceptor 的话,那么就不会被拦截。举个例子吧,有说服力一点。

先修改我们的控制器方法如下:(/**的含义是任意多层目录,只要最后是/testInterceptor就行。)

    @RequestMapping("/**/testInterceptor")
    public ModelAndView testInterceptor(ModelAndView modelAndView){
        System.out.println("Controller方法被执行");
        modelAndView.setViewName("success");
        return modelAndView;
    }

然后我们重启服务器:
访问http://localhost:8080/interceptor/testInterceptor,
SpringMVC拦截器最详细学习笔记_第9张图片
后台显示如下:
SpringMVC拦截器最详细学习笔记_第10张图片
再次访问http://localhost:8080/interceptor/hello/testInterceptor
SpringMVC拦截器最详细学习笔记_第11张图片
后台显示如下:
SpringMVC拦截器最详细学习笔记_第12张图片
如果我们要用这种mvc:interceptors标签配置拦截器的方式拦截所有的控制器方法怎么办呢?很简单,只要把 mvc:mapping 标签的 path属性 中的/*改成/**就好了,如下:

        <mvc:interceptor>
            
            <mvc:mapping path="/**"/>
            
            <mvc:exclude-mapping path="/"/>
            
            <ref bean="myInterceptor"/>
        mvc:interceptor>

8.3 多个拦截器的执行顺序

假如我们现在再建一个SecondInterceptor类,让他也实现HandlerInterceptor接口,然后把springMVC配置文件的内容改为:

    <mvc:interceptors>
        <ref bean="myInterceptor"/>
        <ref bean="secondInterceptor"/>
    mvc:interceptors>

他的执行顺序的话,我们就直接扒源码看吧:
情况一、首先,针对我们所有拦截器都放行的情况,也就是pre都返回true。
之前说过我们的拦截器的底层执行方法被放在DispatcherServlet类中。那我们就去里面看看:
preHandle的如下:(这个跟for语句看着熟悉吧,就是一个简单的递增遍历的过程)
其实就是哪个拦截器先配置的,就先执行哪个。上面的例子就是先执行myInterceptor的pre方法,然后再执行secondInterceptor的pre方法。
SpringMVC拦截器最详细学习笔记_第13张图片
然后执行控制器方法,这个就不用讲了吧。
然后post是递减的一个遍历,也就是先执行second拦截器、再执行myInterceptor拦截器。
SpringMVC拦截器最详细学习笔记_第14张图片
再看我们的after如下:同样是一个递减的遍历,所以也是先执行second拦截器,再执行myInterceptor拦截器。
SpringMVC拦截器最详细学习笔记_第15张图片
总结一下:拦截器的执行顺序
1、pre是先配置,先执行(从第一个开始)
2、post和after是后配置,先执行(从最后一个开始)

情况二、如果中间有某个拦截器的pre返回false
假如现在我们的second拦截器的pre方法返回的是false,那么我们会执行什么呢,直接说结果吧。
首先,我们的pre肯定都会执行,然后我们的控制器方法肯定不会执行,因为他遇到pre是false的情况,他就return出来了。但是拦截器的其他的post和after方法呢?先说结果吧,答案是所有pre返回true的拦截器,他的after是会执行的。来,我们看源码:
首先,我们知道这个拦截器的pre方法的执行顺序是递增的,所以这个this.interceptorIndex也一直在递增,一直递增到某个拦截器返回的是false,这个值就停下来了。然后我们会直接跳转到triggerAfterCompletion方法中。我们直接进去看。
SpringMVC拦截器最详细学习笔记_第16张图片
刚才的this.interceptorIndex在这里就派上用场了,我们知道after 他是递减的一个遍历过程,递减后,他会等于最后一个true的拦截器的索引值,然后从那个拦截器开始一个一个的执行他对应的afterCompletion。
SpringMVC拦截器最详细学习笔记_第17张图片
总结:如果多个拦截器中有某个拦截器的preHandler返回false,那么
1、在他后边执行的拦截器的preHandle都不执行。
2、控制器方法和所有拦截器的post方法不执行
3、返回false的拦截器之前的拦截器的after方法都会执行
在这里插入图片描述
下面贴个结果,加深一下理解,我写了My、Second和Third三个拦截器,并都在配置文件中完成了注册,其中Second的pre方法返回false。最终的执行结果如下:
SpringMVC拦截器最详细学习笔记_第18张图片

你可能感兴趣的:(SpringMVC,mvc,spring)