时间:2020-10-12 19:22:41 | 栏目:JAVA代码 | 点击:次
上篇博文我在博客中讲到如何使用spring MVC框架来实现文件的上传和下载,今天小钱给大家再来分享和介绍Spring MVC框架中相当重要的一块功能――拦截器。
关于拦截器的概念我在这里就不多说了,大家可以上网百度或者看别人写的具体博客,我今天要说的是拦截器在实际开发中它有什么作用,怎样用Spring MVC拦截器来实现可拔插方式管理各种功能。Interceptor拦截器,它的主要作用就是拦截用户的请求并进行相应的处理。什么意思呢?比如说:通过拦截器来进行用户的权限验证,或者是用来判断用户是否已经登录等。
Spring MVC拦截器是可拔插式的设计。如果需要某个拦截器,只需要在配置文件中应用该拦截器即可,如果不需要使用拦截器,只需要在配置文件中取消其应用拦截器。不管是否应用某个拦截器,对Spring MVC框架不会有任何的影响。本文将介绍HandlerInterceptor接口和演示“Spring MVC拦截器实现用户权限验证”项目案例来学习、掌握拦截器。
本项目源码下载:Spring MVC拦截器实现用户权限验证
HandlerInterceptor接口
Spring MVC中的Interceptor拦截器拦截的请求是通过实现HandlerInterceptor接口来完成的。在Spring MVC中定义一个Interceptor拦截器非常简单,通过在要定义的Interceptor拦截器类中实现Spring的HandlerInterceptor接口,或是继承抽象类HandlerInterceptorAdapter。
HandlerInterceptor接口定义了三个方法,Spring MVC就是通过这三个方法来对用户的请求进行拦截处理的,下面对这三个方法进行具体的详解:
(1)boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle)。顾名思义,这个方法将在请求处理之前被调用。Spring MVC中的Interceptor实行的是链式调用,即在一个应用中或者说在一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置的初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是boolean类型的,当返回值为FALSE时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为TRUE时就会继续调用下一个Interceptor的preHandle方法;如果已经是最后一个Interceptor,就会调用当前请求的Controller方法。
(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的执行过程类似。
(3)void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception exception)。 该方法也是在当前所属的Interceptor的preHandle方法的返回值为TRUE时才会执行。顾名思义,该方法将在这个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用就是进行资源整理。
拦截器实现用户权限验证
本文将通过Spring MVC拦截器完成一个用户权限验证的功能。即用户必须登录之后才可以访问这个web网站的首页,如果没有登录就直接访问网站首页,则拦截器会拦截请求,并将请求重新转发到登录页面,同时提示用户“需要先登录再访问网站”,由于是演示案例,所以成功登录之后的网站页面我们直接是拼的一个网页显示给用户。本项目作为测试案例,我就不创建Maven项目了,直接创建的是一个Dynamic Web Project(动态的web项目),项目名称为:Interceptor,本项目采用Tomcat 8作为web服务器,我们需要在项目中引入以下jar包,jar包我就截图演示了,附件源码中lib文件夹下会有,这里直接给一个项目的目录结构,如下图:
首先呢,我们要做一个网页,这个网页就是用来提示用户的登录信息,提示输入用户名和密码,在此我们在项目的WebContent/WEB-INF/content文件夹中创建一个loginForm.jsp,这是一个jsp文件,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>登录页面</title> </head> <body> <h2>用于演示拦截器登录页面</h2> <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>
这时我们需要进行处理/login的请求,我们需要写请求的功能代码,在src目录下创建“cn.edu.jit.controller”包,在创建一个UserController类,用来处理用户请求。具体代码如下:
package cn.edu.jit.controller; import javax.servlet.http.HttpSession; import cn.edu.jit.domain.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; /** * 处理用户请求控制器 */ @Controller public class UserController { /** * 处理/login请求 */ @RequestMapping(value="/login") public ModelAndView login(String loginname,String password,ModelAndView mv,HttpSession session) { //模拟数据库根据登录名和密码查找用户,判断用户登录 if(loginname != null && loginname.equals("钱春华") && 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; } }
UserControlle类的login方法用来处理登录的请求,本项目没有使用数据库存储数据,只是简单的模拟了用户的登录,只要用户输入登录名是“钱春华”,密码是“123456”,则验证通过,并创建一个User对象保存到HttpSession当中,同时将请求使用客户端跳转到main请求:如果登录失败提示信息到ModelAndView对象,同时将请求使用客户端跳转到loginFrom请求,即登录页面。
同样地,我们刚刚写了一个用户的登录请求,我们接下来要处理/main请求,用于实现登录之后给用户显示书的详细定价以及出版信息,我们还在“cn.edu.jit.controller”包中创建一个名为BookController类,用于处理图书请求。具体代码如下:
package cn.edu.jit.controller; import java.util.ArrayList; import java.util.List; import cn.edu.jit.domain.Book; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; /** * 处理图书请求控制器 * @author 钱春华 */ @Controller public class BookController { /** * 处理/main请求 */ @RequestMapping(value="/main") public String main(Model model) { //模拟数据库获得所有图书集合 List<Book> book_list = new ArrayList<Book>(); book_list.add(new Book("java.jpg","JAVA核心技术1","周立新 编著",90.00)); book_list.add(new Book("ee.jpg","Android第一行代码","郭霖 编著",72.50)); book_list.add(new Book("android.jpg","Spring+MyBatis企业应用实战","李刚 编著",58.00)); book_list.add(new Book("ajax.jpg","SpringMVC实战","Alex Bretet 编著",99.00)); //将图书集合添加到model当中 model.addAttribute("book_list", book_list); //跳转到main页面 return "main"; } }
BookController类中的main方法用来处理网站首页的请求,该方法获得所有图书的信息,并将它们设置到Model当中,然后传递到main页面。由于本案例没有使用数据库存储数据,只是简单的创建了一个集合模拟从数据库获取图书信息。
下面是登录成功后访问网页的main.jsp代码,该jsp代码位于WebContent/WEB-INF/content文件夹中。如下具体代码所示:
<%@ 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 PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <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> <h2 align="center">欢迎[${sessionScope.user.username }]访问</h2> <br> <table border="1" align="center"> <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>
接下来,我将设计拦截器验证用户是否登录,如果用户没有登录,不可以访问除登录页面和登录请求的所有Controller。我们在src文件下创建名为“cn.edu.jit.interceptor”这个包,在这个包下创建“AuthorizationInterceptor”类,用于演示拦截器验证用户是否登录。具体代码如下:
package cn.edu.jit.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import cn.edu.jit.domain.User; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 拦截器必须实现HandlerInterceptor接口 */ public class AuthorizationInterceptor 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("AuthorizationInterceptor afterCompletion --> "); } /** * 该方法将在Controller的方法调用之后执行, 方法中可以对ModelAndView进行操作 , * 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行。 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception { System.out.println("AuthorizationInterceptor postHandle --> "); } /** * preHandle方法是进行处理器拦截用的,该方法将在Controller处理之前进行调用, * 该方法的返回值为true拦截器才会继续往下执行,该方法的返回值为false的时候整个请求就结束了。 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("AuthorizationInterceptor 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("AuthorizationInterceptor拦截请求:"); request.setAttribute("message", "请先登录再访问网站"); request.getRequestDispatcher("loginForm").forward(request, response); } else { //如果用户已经登录,则验证通过,放行 System.out.println("AuthorizationInterceptor放行请求:"); flag = true; } } return flag; } }
我们需要在springmvc-config.xml文件中配置拦截器,配置代码具体如下:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/*"/> <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 --> <bean class="cn.edu.jit.interceptor.AuthorizationInterceptor"/> </mvc:interceptor> </mvc:interceptors>
我们在web.xml中配置代码如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>MultipartFileTest</display-name> <!-- 定义Spring MVC的前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- 让Spring MVC的前端控制器拦截所有请求 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
我们来部署Interceptor这个Web应用,在浏览器中输入如下URL来测试应用:
http://localhost:8088/Interceptor
按Enter(回车),出现以下页面,如果没有登录,直接访问main请求,拦截器会拦截请求,验证用户是否登录,此时用户若没有登录,则跳转到登录页面。如下图:
此时我若输入用户名:“钱春华”,密码(假设输入错误的密码)为:“123123”,则拦截器会拦截请求,并将请求重新转发到登录页面,同时提示用户“需要先登录再访问网站”。如下图所示:
输入正确的用户名“钱春华”,密码为“123456”后,显示用户登录成功,而后跳转到网页的首页。如下图所示:
本文中的一些功能案例代码和配置文件不是很完整,下面附上完整代码:
FormController类完整的代码如下:
package cn.edu.jit.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; /** * 动态页面跳转控制器 */ @Controller public class FormController { @RequestMapping(value="/{formName}") public String loginForm(@PathVariable String formName){ //动态跳转页面 return formName; } }
在src文件下创建“cn.edu.jit.domain”包,用于存放图书和用户的两个实例,具体代码如下:
Book类代码:
package cn.edu.jit.domain; import java.io.Serializable; /** * * @author 钱春华 * */ public class Book implements Serializable{ private Integer id;//id private String name;//书名 private String author;//作者 private Double price;//价格 private String image;//封面图片 public Book() { super(); } public Book( String image,String name, String author, Double price) { super(); this.image = image; this.name = name; this.author = author; this.price = price; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } @Override public String toString() { return "Book [id=" + id + ", name=" + name + ", author=" + author + ", price=" + price + ", image=" + image + "]"; } }
User类代码:
package cn.edu.jit.domain; import java.io.Serializable; public class User implements Serializable { private Integer id;//id private String loginname;//登录名 private String password;//密码 private String username;//用户名 public User() { super(); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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; } @Override public String toString() { return "User [id=" + id + ", loginname=" + loginname + ", password=" + password + ", username=" + username + "]"; } }
springmvc-config.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:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <!-- spring可以自动去扫描base-pack下面的包或者子包下面的java文件, 如果扫描到有Spring的相关注解的类,则把这些类注册为Spring的bean --> <context:component-scan base-package="cn.edu.jit.controller"/> <!-- 设置默认配置方案 --> <mvc:annotation-driven/> <!-- 使用默认的Servlet来响应静态文件 --> <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> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/*"/> <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截所有的请求 --> <bean class="cn.edu.jit.interceptor.AuthorizationInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
web.xml配置文件具体配置信息如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>MultipartFileTest</display-name> <!-- 定义Spring MVC的前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- 让Spring MVC的前端控制器拦截所有请求 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>