Spring MVC拦截器简介 |
SpringMVC 中的 Interceptor 拦截器的主要作用就是拦截用户的 url 请求,并在执行 handler方法(控制器方法
)的前中后加入某些特殊请求,例如通过拦截器进行权限验证、记录请求信息的日志、判断用户是否登录等。类似于 Servlet 开发中的过滤器Filter。
使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。
Spring MVC 拦截器的实现一般有两种方法:
1、继承 HandlerInterceptorAdapter
抽象类 2、实现 HandlerInterceptor
接口
HandlerInterceptor接口定义了三个方法
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
preHandle():该方法在请求到达Handler(Controller
)之前,先执行的前置处理方法。当其返回值为true时,表示继续向下执行;当其返回值为false时,请求直接返回,并中断所有后续操作,不会再调用其他拦截器方法,也不会调用handler方法(控制器方法
)。该方法主要用于拦截请求。
postHandle():该方法在控制器方法调用之后,视图解析之前执行。可以通过该方法对请求域之中的 model 和 view 作出进一步的修改。
afterCompletion():该方法在整个请求完成,即DispatcherServlet 渲染了对应的视图之后执行。该方法一般用于实现一些资源清理、记录日志信息等工作。
Spring MVC拦截器的执行流程 |
前端控制器DispatcherServlet在接收客户端发送的request请求后,交给对应的Controller进行处理,在这个过程中,如果配置了拦截器,就会先调用preHandler()方法进行拦截。
多个拦截器的执行顺序 |
Spring MVC中的Interceptor是呈链式调用的,设置多个拦截器时,先按顺序调用preHandler()方法,然后按逆序调用每个拦截器的postHandler()和afterCompletion方法,其中preHandler()方法的执行顺序(拦截器的执行顺序
)是由dispatcher-servlet.xml中<mvc:interceptor>
节点的配置顺序决定的。
具体内容 |
在dispatcher-servlet.xml中配置拦截器
<mvc:interceptors>
:是拦截器的外部标签,用于配置一组拦截器
<mvc:interceptor>
:用来指定一个具体的拦截器
<mvc:mapping path="/**"/>
:对任何URL路径进行拦截, /**代表对所有的URL
<mvc:mapping path="/XX/xx"/>
:拦截所有以/say结尾的URL请求,XX为类上的映射路径,xx为方法上的映射路径,如果类上和方法上都有映射路径,那么path属性中两个路径必须都包含。
<mvc:exclude-mapping path="/XX/xx"/>
:不拦截以/XX/xx结尾的URL请求,XX为类上的映射路径,xx为方法上的映射路径,如果类上和方法上都有映射路径,那么path属性中两个路径必须都包含。
<!--配置拦截器-->
<mvc:interceptors>
<!--拦截器1-->
<mvc:interceptor>
<!-- 对任何url路径进行拦截, /**代表对所有的路径 -->
<mvc:mapping path="/**"/>
<!--拦截所有以/CD4356/say结尾的路径请求-->
<mvc:mapping path="/CD4356/say"/>
<!--不拦截以/CD4356/say结尾的路径请求-->
<mvc:exclude-mapping path="/CD4356/say"/>
<!-- 定义在<mvc:interceptor/>内指定其为拦截器 -->
<bean class="com.CD4356.controller.Interceptor1"/>
</mvc:interceptor>
<!-- 当设置多个拦截器时,先按顺序调用preHandle方法,
然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
<!--拦截器2-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.CD4356.controller.Interceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
配置自定义拦截器类的<bean>
,当然也可在拦截器类上添加@Service注解,效果都是一样的
<bean id="interceptor1" class="com.CD4356.controller.Interceptor1"/>
<bean id="interceptor2" class="com.CD4356.controller.Interceptor2"/>
dispatcher-servlet.xml全部内容
<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--component-scan告诉Servlet去哪里找Controller, base-package指定src目录下存放Controller的包-->
<!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件,
如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean -->
<context:component-scan base-package="com.CD4356.controller"/>
<!--配置视图解析器-->
<!--InternalResourceViewResolver类位于spring-webmvc-4.3.18.RELEASE.jar包中-->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 视图的路径 -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 视图名称后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
<bean id="interceptor1" class="com.CD4356.controller.Interceptor1"/>
<bean id="interceptor2" class="com.CD4356.controller.Interceptor2"/>
<!--配置拦截器-->
<mvc:interceptors>
<!--拦截器1-->
<mvc:interceptor>
<!-- 对任何url路径进行拦截, /**表示拦截所有路径 -->
<mvc:mapping path="/**"/>
<!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 -->
<bean class="com.CD4356.controller.Interceptor1"/>
</mvc:interceptor>
<!-- 当设置多个拦截器时,先按顺序调用preHandle方法,
然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->
<!--拦截器2-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.CD4356.controller.Interceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
web.xml内容
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--加载spring配置文件applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<!--ContextLoaderListener类位于spring-web-4.3.18.RELEASE.jar包-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--表示允许访问静态的*.jpg文件(png、git等格式的图片需另行配置)
为什么要加这一段呢? 因为配置springmvc的servlet的时候,使用的路径是"/",
导致静态资源在默认情况下不能访问,所以要加上这一段,允许访问jpg,并且必须加在springmvc的servlet之前
如果你配置spring-mvc使用的路径是/*.do,就不会有这个问题了
-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<!--配置Spring MVC的入口 DispatcherServlet, 把所有的请求都提交到该Servlet-->
<servlet>
<servlet-name>dispatcher</servlet-name>
<!--DispatcherServlet类位于spring-webmvc-4.3.18.RELEASE.jar包-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置springmvc的前端控制器,可以配置多个前端控制器来拦截不同的url -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!--将*.form修改成/,就可以拦截所有的url请求-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
新建一个测试类HiController
package com.CD4356.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller("hiController") /*@Controller表明这是一个controller类型的bean*/
@RequestMapping("/CD4356")
public class HiController {
@RequestMapping(value = "/say")
public String hello(Model model){ //参数中传入Model
model.addAttribute("name","CD4356");
model.addAttribute("url","https://blog.csdn.net/weixin_42950079");
System.out.println("handler方法...");
return "say"; //返回页面模板的名字,到指定的目录下寻找该文件名的文件
}
}
在/WEB-INF/pages/目录下创建say.jsp文件,内容如下
<%--
Created by IntelliJ IDEA.
User: CD4356
Date: 2019/3/5
Time: 14:34
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>springmvc项目测试</title>
</head>
<div style="background-color: cadetblue;width: 100px;height: 40px">author: ${name}</div><br>
<font face="华文中宋" size="5">CSDN: ${url}</font>
</body>
</html>
新建一个拦截器Interceptor1,实现HandlerInterceptor接口,并实现接口的方法
package com.CD4356.controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class Interceptor1 implements HandlerInterceptor {
/**
* 在业务处理器处理请求之前被调用
* 如果返回false
* 从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链
* 如果返回true
* 执行下一个拦截器,直到所有的拦截器都执行完毕
* 再执行被拦截的Controller
* 然后进入拦截器链,
* 从最后一个拦截器往回执行所有的postHandle()
* 接着再从最后一个拦截器往回执行所有的afterCompletion()
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o) throws Exception {
System.out.println("preHandle1(), 在访问Controller之前被调用");
return true;
}
/**
* 在业务处理器处理请求执行完成后,生成视图之前执行的动作
* 可在modelAndView中加入数据,比如当前时间
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle1(), 在访问Controller之后,访问视图之前被调用,这里可以注入一个时间到modelAndView中,用于后续视图显示");
modelAndView.addObject("date","由拦截器生成的时间:" + new Date());
}
/**
* 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
*
* 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion()
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception e) throws Exception {
System.out.println("afterCompletion1(), 在访问视图之后被调用");
} // 拦截器类HandlerInterceptorAdapter
}
新建第二个拦截器Interceptor2,实现HandlerInterceptor接口,并实现接口的方法
package com.CD4356.controller;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class Interceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("preHandle2(), 在访问Controller之前被调用");
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2(), 在访问Controller之后,访问视图之前被调用,这里可以注入一个时间到modelAndView中,用于后续视图显示");
modelAndView.addObject("date","由拦截器生成的时间:" + new Date());
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("afterCompletion2(), 在访问视图之后被调用");
}
}
在浏览器中输入 http://localhost:8080/CD4356/say 后打开页面后,IDEA控制台的输出内容如下
该输出结果证明了前面所说的多个拦截器的执行顺序
修改Interceptor1的preHandle()方法,让其返回值为false,然后在浏览器中输入 http://localhost:8080/CD4356/say 后打开页面后,IDEA控制台的输出内容如下
验证了preHandle()返回值为false后,就会中断后续所有操作,包括handler方法和postHandler()、afterCompletion()拦截器方法的调用
修改Interceptor2的preHandle()方法,让其返回值为false,然后在浏览器中输入 http://localhost:8080/CD4356/say 后打开页面后,IDEA控制台的输出内容如下