7.3 拦截器 Interceptor
拦截器是SpringMVC
中相当重要的功能,它的主要作用是拦截用户的请求并进行相应的处理 。比如**通过拦截器来进行用户权限验证 ,或者用来判断用户是否已经登录 **等。
Spring MVC拦截器可插拔 Spring MVC
拦截器是可插拔式的设计。如果需要使用某个拦截器,只需要在配置文件中应用该拦截器即可 ;如果不需要使用该拦截器,只需要在配置文件中取消应用该拦截器。不管是否应用某个拦截器,对Spring MVC
框架不会有任何影响。
7.3.1 HandlerInterceptor接口 Spring MVC
中的Interceptor
拦截器拦截请求是通过实现HandlerInterceptor
接口来完成的 。在Spring MVC
中定义一个Interceptor
拦截器非常简单,通常在要定义的Interceptor
拦截器类中实现Spring
的HandlerInterceptor
接口,或者继承抽象类HandlerInterceptorAdapter
。HandlerInterceptor
接口中定义了三个方法,SpringMVC
就是通过这三个方法来对用户的请求进行拦截处理的。
preHandle方法 preHandle方法的方法声明如下
1 boolean preHandle (HttpServletRequest request,HttpServletResponse response,Object handle)
顾名思义,该方法将在请求处理之前被调用 。SpringMVC
中的Interceptor
实行的是链式调用 ,即在一个应用中或者说在一个请求中可以同时存在多个Interceptor
。每个Interceptor
的调用会依据它的声明顺序依次执行,而且最先执行的是Interceptor
中的preHandle
方法,所以可以在这个方法中进行一些前置的初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是boolean
类型的,
当返回值为false
时,表示请求结束 ,后续的Interceptor
和Controller
都不会再执行;
当返回值为true
时就会继续调用下一个Interceptor
的preHandle
方法;
如果已经是最后一个Interceptor
,就会调用当前请求的Controller
方法。
postHandle方法 1 2 void postHandle (HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView mv)
该方法和之后的afterCompletion
方法都只能在当前所属的Interceptor
的preHandle
方法的返回值为true
时才能被调用。postHandle
方法,顾名思义,就是在当前请求被处理之后,也就是在Controller
的请求处理方法被调用之后执行 ,但是它会在DispatcherServlet
进行视图返回渲染之前被调用 ,所以我们可以在这个方法中对Controller
处理之后的ModelAndView
对象进行操作。postHandle
方法被调用的方向跟preHandle
是相反的,也就是说先声明的Interceptor
的postHandle
方法反而会后执行,这和Struts2
里面的Interceptor
的执行过程类似。
afterCompletion方法 1 2 void afterCompletion (HttpServletRequest request,HttpServletResponsere sponse, Object handler,Exception exception)
该方法也是在当前所属的Interceptor
的preHandle
方法的返回值为true
时才会执行。
顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet
渲染了对应的视图之后执行 。这个方法的主要作用是进行资源清理 。
示例 拦截器实现用户权限验证 目录结构 展开/折叠
G:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\07\InterceptorTest
├─src\
│ └─org\
│ └─fkit\
│ ├─controller\
│ │ ├─BookController.java
│ │ ├─FormController.java
│ │ └─UserController.java
│ ├─domain\
│ │ ├─Book.java
│ │ └─User.java
│ └─interceptor\
│ └─AuthorizationInterceptor.java
└─WebContent\
├─images\
│ ├─ajax.jpg
│ ├─android.jpg
│ ├─basic.jpg
│ ├─ee.jpg
│ ├─fkjava.jpg
│ ├─framework.jpg
│ ├─java.jpg
│ ├─javaee.jpg
│ ├─struts.jpg
│ └─xml.jpg
├─META-INF\
│ └─MANIFEST.MF
└─WEB-INF\
├─content\
│ ├─loginForm.jsp
│ └─main.jsp
├─lib\
│ ├─commons-logging-1.2.jar
│ ├─javax.servlet.jsp.jstl-1.2.1.jar
│ ├─javax.servlet.jsp.jstl-api-1.2.1.jar
│ ├─spring-aop-5.0.1.RELEASE.jar
│ ├─spring-aspects-5.0.1.RELEASE.jar
│ ├─spring-beans-5.0.1.RELEASE.jar
│ ├─spring-context-5.0.1.RELEASE.jar
│ ├─spring-context-indexer-5.0.1.RELEASE.jar
│ ├─spring-context-support-5.0.1.RELEASE.jar
│ ├─spring-core-5.0.1.RELEASE.jar
│ ├─spring-expression-5.0.1.RELEASE.jar
│ ├─spring-instrument-5.0.1.RELEASE.jar
│ ├─spring-jcl-5.0.1.RELEASE.jar
│ ├─spring-jdbc-5.0.1.RELEASE.jar
│ ├─spring-jms-5.0.1.RELEASE.jar
│ ├─spring-messaging-5.0.1.RELEASE.jar
│ ├─spring-orm-5.0.1.RELEASE.jar
│ ├─spring-oxm-5.0.1.RELEASE.jar
│ ├─spring-test-5.0.1.RELEASE.jar
│ ├─spring-tx-5.0.1.RELEASE.jar
│ ├─spring-web-5.0.1.RELEASE.jar
│ ├─spring-webflux-5.0.1.RELEASE.jar
│ ├─spring-webmvc-5.0.1.RELEASE.jar
│ └─spring-websocket-5.0.1.RELEASE.jar
├─springmvc-config.xml
└─web.xml
本小节通过拦截器完成一个用户权限验证的功能。即用户必须登录之后才可以访问网站首页 ,如果没有登录就直接访问网站首页,则拦截器会拦截请求,并将请求重新转发到登录页面,同时提示用户需要先登录再访问网站。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Controller public class FormController { @GetMapping(value = "/loginForm") public String loginForm () { System.out.println("请求处理方法 loginForm" ); return "loginForm" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" > <title>登录页面</title> </head> <body> <h3>登录页面</h3> <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> </body> </html>
UserController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package org.fkit.controller;import javax.servlet.http.HttpSession;import org.fkit.domain.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.servlet.ModelAndView;@Controller public class UserController { @PostMapping(value = "/login") public ModelAndView login (String loginname, String password, ModelAndView mv, HttpSession session) { if (loginname != null && loginname.equals("fkit" ) && password != null && password.equals("123456" )) { User user = new User (); user.setLoginname(loginname); user.setPassword(password); user.setUsername("管理员" ); session.setAttribute("user" , user); mv.setViewName("redirect:main" ); } else { mv.addObject("message" , "登录名或密码错误,请重新输入!" ); mv.setViewName("loginForm" ); } return mv; } }
UserController
类的login
方法用来处理登录请求,本示例没有使用数据库存储数据,只是简单地模拟了用户登录,只要用户输入的登录名是”xiaoming
“,密码是”xiaoming”,则验证通过。
如果验证通过,创建一个User
对象保存到HttpSession
当中,同时将请求使用客户端跳转到main
请求;
如果验证失败,则设置失败提示信息到ModelAndView
对象,同时将请求使用客户端跳转到loginForm
请求,即跳回登录页面。
BookController.java BookController
类的main
方法用来处理网站首页的请求,该方法获得所有图书信息,并将它们设置到Model
当中,然后传递到main
页面。本示例没有使用数据库存储数据,只是简单地创建了一个集合模拟从数据库获取图书信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package org.fkit.controller;import java.util.ArrayList;import java.util.List;import org.fkit.domain.Book;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;@Controller public class BookController { @RequestMapping(value = "/main") public String main (Model model) { List<Book> book_list = new ArrayList <Book>(); book_list.add(new Book ("java.jpg" , "疯狂Java讲义(附光盘)" , "李刚 编著" , 74.2 )); book_list.add(new Book ("ee.jpg" , "轻量级Java EE企业应用实战" , "李刚 编著" , 59.2 )); book_list.add( new Book ("android.jpg" , "疯狂Android讲义(附光盘)" , "李刚 编著" , 60.6 )); book_list.add(new Book ("ajax.jpg" , "疯狂Ajax讲义(附光盘)" , "李刚 编著" , 66.6 )); model.addAttribute("book_list" , book_list); return "main" ; } }
main.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" > <title>首页</title> <style type="text/css" > table { border-collapse: collapse; border-spacing: 0 ; border-left: 1px solid #888 ; border-top: 1px solid #888 ; background: #efefef; } th, td { border-right: 1px solid #888 ; border-bottom: 1px solid #888 ; padding: 5px 15px; } th { font-weight: bold; background: #ccc; } </style> </head> <body> <h3>欢迎[${sessionScope.user.username }]访问</h3> <br> <table border="1" > <tr> <th>封面</th> <th>书名</th> <th>作者</th> <th>价格</th> </tr> <c:forEach items="${requestScope.book_list }" var ="book" > <tr> <td><img src="images/${book.image }" height="60" ></td> <td>${book.name }</td> <td>${book.author }</td> <td>${book.price }</td> </tr> </c:forEach> </table> </body> </html>
拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class AuthorizationInterceptor implements HandlerInterceptor { private static final String[] IGNORE_URI = {"/loginForm" , "/login" }; @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception { System.out.println("拦截器的 afterCompletion方法\n-----------------------------" ); } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception { System.out.println("拦截器的 postHandle方法" ); } @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器的 preHandle方法" ); boolean flag = false ; String servletPath = request.getServletPath(); for (String s : IGNORE_URI) { if (servletPath.contains(s)) { flag = true ; break ; } } if (!flag) { User user = (User) request.getSession().getAttribute("user" ); if (user == null ) { System.out.println(" |----验证不通过,重新登录" ); request.setAttribute("message" , "请先登录再访问网站" ); request.getRequestDispatcher("loginForm" ).forward(request, response); } else { System.out.println(" |----验证通过,放行/main请求" ); flag = true ; } } return flag; } }
测试 1 <a href ="main" > 直接请求/main</a >
如果没有登录,直接访问main
请求,拦截器会拦截请求,验证用户是否登录,此时用户若没有登录,则跳转到登录页面,如下图所示: 从控制台的输出可以看到各个方法的执行顺序,如下所示.
1 2 3 4 5 6 7 拦截器的 preHandle方法 |----验证不通过,重新登录 拦截器的 preHandle方法 请求处理方法 loginForm 拦截器的 postHandle方法 拦截器的 afterCompletion方法 -----------------------------
此时填写正确的用户名xiaoming和密码xiaoming,点击登录 此时才可以正确得到main请求的资源,如下图所示: 控制台新增内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 拦截器的 preHandle方法 请求处理方法 login |----->登陆验证通过,转发到main请求 拦截器的 postHandle方法 拦截器的 afterCompletion方法 ----------------------------- 拦截器的 preHandle方法 |----验证通过,放行/main请求 请求处理方法 main 拦截器的 postHandle方法 拦截器的 afterCompletion方法 -----------------------------
可以看到执行顺序是,表单提交到login
请求,login
方法验证成功后,转发到main
请求,当请求main
时,拦截器的preHandle
方法先验证是否登录过,现在的情况是已经登录了,preHandle
方法放行main
请求,main
方法得到执行.