SpringMVC中的拦截器主要用于拦截控制器方法的执行。要想写一个自己定义的拦截器,我们需要实现HandlerInterceptor接口(推荐)或者继承HandlerInterceptorAdapter类。
一个浏览器的请求发送到服务器历经的流程如下:
直接扒源码看吧:
首先我们打开DispatcherServlet类,往下拉,拉到533行(导入的包版本不同,对应的行数可能不同),可以看到:
再往下拉,我们可以看到:
首先,我们新建一个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>
首先写一个简单的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中除了用bean标签设置我们自定义的拦截器的类,我们还可以用ref标签设置。用法如下:
首先,在我们的拦截器类上添加注解@Component
然后,在springMVC配置文件中,修改我们的拦截器配置如下:
<mvc:interceptors>
<ref bean="myInterceptor">ref>
mvc:interceptors>
效果跟前面一毛一样,这里就不再掩饰了。
只需要在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>
配置完成后,我们重启服务器。
启动我们的首页,不再拦截。
然后我们点击超链接进去,成功拦截:
这里特别需要注意的是,下面这个标签设置的是拦截所有只有一层目录的控制器方法
也就是说现在如果这个控制器方法的路径是 工程路径/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,
后台显示如下:
再次访问http://localhost:8080/interceptor/hello/testInterceptor
后台显示如下:
如果我们要用这种mvc:interceptors标签配置拦截器的方式拦截所有的控制器方法怎么办呢?很简单,只要把 mvc:mapping 标签的 path属性 中的/*改成/**就好了,如下:
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/"/>
<ref bean="myInterceptor"/>
mvc:interceptor>
假如我们现在再建一个SecondInterceptor类,让他也实现HandlerInterceptor接口,然后把springMVC配置文件的内容改为:
<mvc:interceptors>
<ref bean="myInterceptor"/>
<ref bean="secondInterceptor"/>
mvc:interceptors>
他的执行顺序的话,我们就直接扒源码看吧:
情况一、首先,针对我们所有拦截器都放行的情况,也就是pre都返回true。
之前说过我们的拦截器的底层执行方法被放在DispatcherServlet类中。那我们就去里面看看:
preHandle的如下:(这个跟for语句看着熟悉吧,就是一个简单的递增遍历的过程)
其实就是哪个拦截器先配置的,就先执行哪个。上面的例子就是先执行myInterceptor的pre方法,然后再执行secondInterceptor的pre方法。
然后执行控制器方法,这个就不用讲了吧。
然后post是递减的一个遍历,也就是先执行second拦截器、再执行myInterceptor拦截器。
再看我们的after如下:同样是一个递减的遍历,所以也是先执行second拦截器,再执行myInterceptor拦截器。
总结一下:拦截器的执行顺序
1、pre是先配置,先执行(从第一个开始)
2、post和after是后配置,先执行(从最后一个开始)
情况二、如果中间有某个拦截器的pre返回false
假如现在我们的second拦截器的pre方法返回的是false,那么我们会执行什么呢,直接说结果吧。
首先,我们的pre肯定都会执行,然后我们的控制器方法肯定不会执行,因为他遇到pre是false的情况,他就return出来了。但是拦截器的其他的post和after方法呢?先说结果吧,答案是所有pre返回true的拦截器,他的after是会执行的。来,我们看源码:
首先,我们知道这个拦截器的pre方法的执行顺序是递增的,所以这个this.interceptorIndex也一直在递增,一直递增到某个拦截器返回的是false,这个值就停下来了。然后我们会直接跳转到triggerAfterCompletion方法中。我们直接进去看。
刚才的this.interceptorIndex在这里就派上用场了,我们知道after 他是递减的一个遍历过程,递减后,他会等于最后一个true的拦截器的索引值,然后从那个拦截器开始一个一个的执行他对应的afterCompletion。
总结:如果多个拦截器中有某个拦截器的preHandler返回false,那么
1、在他后边执行的拦截器的preHandle都不执行。
2、控制器方法和所有拦截器的post方法不执行
3、返回false的拦截器之前的拦截器的after方法都会执行
下面贴个结果,加深一下理解,我写了My、Second和Third三个拦截器,并都在配置文件中完成了注册,其中Second的pre方法返回false。最终的执行结果如下: