11.1 Spring MVC 拦截器
Interceptor 拦截器也是 Spring MVC 中相当重要的功能,它的主要作用是拦截用户的请求并进行相应的处理。Spring MVC 拦截器是可插拔的设计。如果需要使用某个拦截器,只需要在配置文件中应用该拦截器即可;如果不需要使用该拦截器,只需要在配置文件中取消应用该拦截器。不管是否应用某个拦截器,对 Spring MVC 框架不会有任何影响。
11.1.1 HandlerInterceptor 接口
Spring MVC 中的 Interceptor 拦截器拦截请求是通过实现 HandlerInterceptor 接口来完成的。在 Spring MVC 中定义一个 Interceptor 拦截器非常简单,通常在要定义的 Interceptor 拦截器中实现 Spring 的 HandlerInterceptor 接口,或者继承抽象类 HandlerInterceptorAdapter。
HandlerInterceptor 接口定义了三个方法,Spring MVC 就是通过这三个方法来对用户的请求进行拦截处理的。
- boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle)。该方法将在请求处理之前被调用。Spring MVC 中的 Interceptor 实行的是链式调用。即在一个应用中或者在一个请求中可以同时存在多个 Interceptor。每个 Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的是 Interceptor 中的 preHandle 方法,所以可以在这个方法中进行一些前置的初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是 Boolean 类型的,当返回值是 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时就会继续调用下一个 Interceptor 的 preHandle 方法;如果已经是最后一个 Interceptor,就会调用当前请求的 Controller 方法。
- void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView mv)。该方法和之后的 afterCompletion 方法都只能在当前所属的 Interceptor 的 preHandle 方法的返回值为 true 时才能被调用。postHandle 方法,顾名思义,就是在当前请求被处理之后,也就是 Controller 方法被调用之后执行,但是它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对 Controller 处理之后的 ModleAndView 对象进行操作。postHandle 方法被调用的方向跟 preHandle 时相反的,也就是说先声明的 Interceptor 的 postHandle 方法反而会后执行,这个 Struts2 里面的 Interceptor 的执行过程类似。
- void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception exception)。该方法也是在当前所属的Interceptor 的 preHandle 方法的返回值为 true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在 DispatcherServlet 渲染了对应的视图之后执行。这个方法主要作用是进行资源清理。
11.1.2 拦截器实现用户权限验证
用户必须登录之后才可以访问网站首页,如果没有登陆就直接访问网站首页,则拦截器会拦截请求,并将请求重新转发到登陆页面,同时提示用户需要先登录再访问网站。
- 新建 loginForm.jsp 登录页面。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h3>登陆页面</h3>
<form:form action="login" method="post">
<!-- 提示信息 -->
<font color="red">${requestScope.message }</font>
<table>
<tr>
<td><label>登录名:</label></td>
<td><input type="text" id="loginname" name="loginname"></td>
</tr>
<tr>
<td><label>密码:</label></td>
<td><input type="password" id="password" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="登录"></td>
</tr>
</table>
</form:form>
</body>
</html>
- 新建 UserController 类处理登录请求和页面跳转。
package controller;
import domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpSession;
@Controller
public class UserController {
/*
* 处理 /login 请求
*/
@RequestMapping(value = "/login")
public ModelAndView login(String loginname, String password, ModelAndView mv, HttpSession session){
// 模拟数据库根据登录名和密码查找用户,判断用户登录
if (loginname != null && loginname.equals("ceshi")
&& password != null && password.equals("123456")){
// 模拟创建用户
User user = new User();
user.setLoginname(loginname);
user.setPassword(password);
user.setUsername("管理员");
// 登陆成功,将 user 对象设置到 HttpSession 作用域
session.setAttribute("user",user);
// 转发到 main 请求
mv.setViewName("redirect:main");
}else {
// 登陆失败,设置失败提示信息,并跳转到登陆页面
mv.addObject("message" , "登录名或密码错误,请重新输入!");
mv.setViewName("loginForm");
}
return mv;
}
}
- 新建 PageController 类用于处理页面跳转。
package controller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
private final static Log logger = LogFactory.getLog(PageController.class);
@RequestMapping(value = "/{formName}")
public String pageName(@PathVariable String formName){
return formName;
}
}
- 新建 User 类用于接收页面登录用户信息。
package domain;
import java.io.Serializable;
public class User implements Serializable {
private String loginname;
private String password;
private String username;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
- 新建 TestInterceptor 类,设计拦截器验证用户是否登录,如果没有登陆,不可以访问除登陆页面和登录请求的所有 Controller。
package interceptor;
import domain.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* 拦截器必须实现 HandlerInterceptor 接口
*/
public class TestInterceptor implements HandlerInterceptor {
// 不拦截 "/loginForm" 和 "/login" 请求
private static final String[] IGNORE_URI = {"/loginForm","/login"};
/*
* 该方法将整个请求完成之后执行,主要作用是用于清理资源,
* 该方法也只能在当前 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler,Exception exception)throws Exception{
System.out.println("TestInterceptor afterCompletion -->");
}
/*
* preHandle 方法是进行处理器拦截用的,该方法将在 Controller 处理之前进行调用,
* 该方法的返回值为 true 拦截器才会继续往下执行,该方法的返回值为 false 时整个请求就结束了。*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TestInterceptor preHandle -->");
// flag 变量用于判断用户是否登录,默认为 false。
boolean flag = false;
// 获取请求的路径进行判断。
String servletPath = request.getServletPath();
// 判断请求是否需要拦截
for (String s:IGNORE_URI){
if (servletPath.contains(s)){
flag = true;
break;
}
}
// 拦截请求
if (!flag){
// 1.获取 session 中的用户
User user = (User)request.getSession().getAttribute("user");
// 2. 判断用户是否已经登录
if (user == null){
// 如果用户没有登陆,则设置提示信息,跳转到登陆页面
System.out.println("TestInterceptor 拦截请求!");
request.setAttribute("message" , "请先登录再访问本网站!");
request.getRequestDispatcher("loginForm").forward(request,response);
}else {
// 如果用户已经登录,则验证通过,放行
System.out.println("TestInterceptor 放行请求!");
flag = true;
}
}
return flag;
}
/*
* 该方法将在 Controller 的方法调用之后执行,方法中可以对 ModelAndView 进行操作,
* 该方法也只能在当前 Interceptor 的 preHandle 方法的返回值为 true 时才会执行。
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("TestInterceptor postHandle -->");
}
}
- 修改 Spring MVC 配置文件,增加拦截器配置。
<?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/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<!-- Spring MVC 拦截器定义 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截所有请求 -->
<mvc:mapping path="/*"/>
<!-- 使用 bean 定义一个 Interceptor -->
<bean class="interceptor.TestInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
- 新建 main.jsp 首页。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h3>欢迎[ ${sessionScope.user.username } ] 访问!</h3>
</body>
</html>
- 修改 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">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署应用,在浏览器中输入如下 URL 测试:
http://localhost:8080/InterceptorTest/main
结果如下图:
输入正确的登录名“ceshi”,密码“123456”,点击登录,显示登录成功,跳转到网站首页: