Spring基础知识(18)- Spring MVC (八) | 拦截器(Interceptor)、REST 风格
1. 拦截器(Interceptor)
在系统中,经常需要在处理用户请求之前和之后执行一些行为,例如检测用户的权限,或者将请求的信息记录到日志中,即平时所说的 “权限检测” 及 “日志记录”。当然不仅仅这些,所以需要一种机制,拦截用户的请求,在请求的前后添加处理逻辑。
Spring MVC 提供了 Interceptor 拦截器机制,用于请求的预处理和后处理。
Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
1) 拦截器的定义
在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。
(1) 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义;
(2) 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。
本节以实现 HandlerInterceptor 接口的定义方式为例讲解自定义拦截器的使用方法。示例代码如下。
1 package com.example.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 import org.springframework.web.servlet.HandlerInterceptor; 6 import org.springframework.web.servlet.ModelAndView; 7 8 public class TestInterceptor implements HandlerInterceptor { 9 10 @Override 11 public boolean preHandle(HttpServletRequest request, 12 HttpServletResponse response, Object handler) throws Exception { 13 // preHandle() 方法在控制器的处理请求方法调用之前执行 14 System.out.println("TestInterceptor -> preHandle()"); 15 return false; 16 } 17 18 @Override 19 public void postHandle(HttpServletRequest request, 20 HttpServletResponse response, Object handler, 21 ModelAndView modelAndView) throws Exception { 22 // postHandle() 方法在控制器的处理请求方法调用之后,解析视图之前执行 23 System.out.println("TestInterceptor -> postHandle()"); 24 } 25 26 @Override 27 public void afterCompletion(HttpServletRequest request, 28 HttpServletResponse response, Object handler, Exception e) 29 throws Exception { 30 // afterCompletion() 方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行 31 System.out.println("TestInterceptor -> afterCompletion()"); 32 } 33 }
上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,说明如下。
(1) preHandle( ):该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作;
(2) postHandle( ):该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改;
(3) afterCompletion( ):该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作;
2) 拦截器的配置
让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:
1 <!-- 配置拦截器 --> 2 <mvc:interceptors> 3 <!-- 配置一个全局拦截器,拦截所有请求 --> 4 <bean class="com.example.interceptor.TestInterceptor" /> 5 6 <mvc:interceptor> 7 <!-- 配置拦截器作用的路径 --> 8 <mvc:mapping path="/**" /> 9 <!-- 配置不需要拦截作用的路径 --> 10 <mvc:exclude-mapping path="" /> 11 12 <!-- 定义 <mvc:interceptor> 元素中,表示匹配指定路径的请求才进行拦截 --> 13 <bean class="com.example.interceptor.Interceptor1" /> 14 </mvc:interceptor> 15 16 <mvc:interceptor> 17 <!-- 配置拦截器作用的路径 --> 18 <mvc:mapping path="/test/interceptor" /> 19 20 <!-- 定义在 <mvc:interceptor> 元素中,表示匹配指定路径的请求才进行拦截 --> 21 <bean class="com.example.interceptor.Interceptor2" /> 22 </mvc:interceptor> 23 </mvc:interceptors>
在上述示例代码中,元素说明如下。
<mvc:interceptors>:该元素用于配置一组拦截器。
<bean>:该元素是 <mvc:interceptors> 的子元素,用于定义全局拦截器,即拦截所有的请求。
<mvc:interceptor>:该元素用于定义指定路径的拦截器。
<mvc:mapping>:该元素是 <mvc:interceptor> 的子元素,用于配置拦截器作用的路径,该路径在其属性 path 中定义。path 的属性值为/**时,表示拦截所有路径,值为/gotoTest时,表示拦截所有以/gotoTest结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 <mvc:exclude-mapping> 子元素进行配置。
需要注意的是,<mvc:interceptor> 元素的子元素必须按照 <mvc:mapping.../>、<mvc:exclude-mapping.../>、<bean.../> 的顺序配置。
示例
下面通过拦截器来完成一个用户登录权限验证的 Web 应用。
在 “Spring基础知识(12)- Spring MVC (二)” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。
(1) 创建 src/main/java/com/example/entity/User.java 文件
1 package com.example.entity; 2 3 public class User { 4 private int id; 5 private String username; 6 private String password; 7 8 public User() { 9 10 } 11 12 public int getId() { 13 return id; 14 } 15 16 public void setId(int id) { 17 this.id = id; 18 } 19 20 public String getUsername() { 21 return this.username; 22 } 23 24 public void setUsername(String username) { 25 this.username = username; 26 } 27 28 public String getPassword() { 29 return password; 30 } 31 32 public void setPassword(String password) { 33 this.password = password; 34 } 35 }
(2) View
创建 src/main/webapp/WEB-INF/jsp/auth.jsp 文件
1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 5 <title>Auth</title> 6 </head> 7 <body> 8 <h4>Auth Page</h4> 9 <p>Message: ${message}</p> 10 <p> </p> 11 12 <form action="${pageContext.request.contextPath }/user/auth/post" method="POST"> 13 <p>Username: <input type="text" name="username" /></p> 14 <p>Password: <input type="password" name="password" /></p> 15 <p><input type="submit" value="Submit" /></p> 16 </form> 17 </body> 18 </html>
创建 src/main/webapp/WEB-INF/jsp/main.jsp 文件
1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 5 <title>Main</title> 6 </head> 7 <body> 8 <p>Welcome ${user.username }</p> 9 <p><a href="${pageContext.request.contextPath }/user/logout">Exit</a></p> 10 </body> 11 </html>
(3) 创建 src/main/java/com/example/interceptor/LoginInterceptor.java
1 package com.example.interceptor; 2 3 import javax.servlet.http.HttpServletRequest; 4 import javax.servlet.http.HttpServletResponse; 5 import javax.servlet.http.HttpSession; 6 import org.springframework.web.servlet.HandlerInterceptor; 7 import org.springframework.web.servlet.ModelAndView; 8 9 public class LoginInterceptor implements HandlerInterceptor { 10 11 @Override 12 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 13 Object handler) throws Exception { 14 System.out.println("TestInterceptor -> preHandle()"); 15 16 // URL 17 String url = request.getRequestURI(); 18 if (url.indexOf("/user/auth") >= 0 || url.indexOf("/user/auth/post") >= 0) { 19 return true; 20 } 21 22 // Session 23 HttpSession session = request.getSession(); 24 Object obj = session.getAttribute("user"); 25 if (obj != null) 26 return true; 27 28 // Check auth 29 request.setAttribute("message", "Not authorized"); 30 request.getRequestDispatcher("/user/auth").forward(request, response); 31 32 return false; 33 } 34 35 @Override 36 public void postHandle(HttpServletRequest request, 37 HttpServletResponse response, Object handler, 38 ModelAndView modelAndView) throws Exception { 39 System.out.println("TestInterceptor -> postHandle()"); 40 } 41 42 @Override 43 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 44 Object object, Exception e) throws Exception { 45 System.out.println("TestInterceptor -> afterCompletion()"); 46 } 47 48 }
(4) 修改 springmvc-beans.xml 文件,添加如下配置
1 <!-- 配置拦截器 --> 2 <mvc:interceptors> 3 <mvc:interceptor> 4 <!-- 配置拦截器作用的路径 --> 5 <mvc:mapping path="/**" /> 6 <bean class="com.example.interceptor.LoginInterceptor" /> 7 </mvc:interceptor> 8 </mvc:interceptors>
(5) 创建 src/main/java/com/example/controller/UserController.java 文件
1 package com.example.controller; 2 3 import javax.servlet.http.HttpSession; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.ui.Model; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import com.example.entity.User; 8 9 @Controller 10 @RequestMapping("/user") 11 public class UserController { 12 13 @RequestMapping("/auth") 14 public String auth() { 15 return "auth"; 16 } 17 18 @RequestMapping("/auth/post") 19 public String authPost(User user, Model model, HttpSession session) { 20 if ("admin".equals(user.getUsername()) && "123456".equals(user.getPassword())) { 21 session.setAttribute("user", user); 22 return "redirect:/user/main"; 23 } 24 model.addAttribute("message", "Invalid username or password"); 25 return "auth"; 26 } 27 28 @RequestMapping("/main") 29 public String main() { 30 return "main"; 31 } 32 33 @RequestMapping("/logout") 34 public String logout(HttpSession session) { 35 session.invalidate(); 36 return "auth"; 37 } 38 }
访问:http://localhost:9090/user/main
2. REST 风格
REST(Representational State Transfer)即表述性转移,是目前最流行的一种软件架构风格。它结构清晰、易于理解、有较好的扩展性。
Spring REST 风格可以简单理解为:使用 URL 表示资源时,每个资源都用一个独一无二的 URL 来表示,并使用 HTTP 方法表示操作,即准确描述服务器对资源的处理动作(GET、POST、PUT、DELETE),实现资源的增删改查。
GET:表示获取资源
POST:表示新建资源
PUT:表示更新资源
DELETE:表示删除资源
下面举例说明 REST 风格的 URL 与传统 URL 的区别。
/user/view.jsp?id=3 VS /user/view/3
/user/delete.jsp?id=3 VS /user/delete/3
/user/modify.jsp?id=3 VS /user/modify/3
REST 风格的 URL 中最明显的就是参数不再使用 “?” 传递。这种风格的 URL 可读性更好,使得项目架构清晰,最关键的是 Spring MVC 也提供对这种风格的支持。
REST 风格在开发 API(不需要view)返回 JSON 或 XML 的接口中,一般使用 @RestController (或 &Controller + @ResponseBody),@RestController (或 &Controller + @ResponseBody) 支持 GET、POST、PUT、DELETE 等动作。
在处理 Form 表单方式的开发中,由于 Form 的 method 默认不支持 PUT 和 DELETE 请求,所以需要将 DELETE 和 PUT 请求转换成 POST 请求,在 web.xml 中配置过滤器 HiddenHttpMethodFilter。
本文只讨论 Form 表单方式的 REST 风格, @RestController (或 &Controller + @ResponseBody) 可以参考 “Spring基础知识(17)- Spring MVC (七)” 的 “JSON 数据交互”。
示例
在 “Spring基础知识(12)- Spring MVC (二)” 的示例里,更新过 springmvc-beans.xml 的 SpringmvcBasic 项目基础上,修改如下。
(1) 创建 src/main/webapp/WEB-INF/jsp/rest.jsp 文件
1 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 5 <title> REST Style</title> 6 </head> 7 <body> 8 9 <h4>发送 GET 请求</h4> 10 <p><a href="/user/rest/1">GET</a></p> 11 12 <hr /> 13 <h4>发送 POST 请求</h4> 14 <form action="/user/rest/1" method="POST"> 15 <p><input type="submit" value="POST" /></p> 16 </form> 17 18 <hr /> 19 <h4>发送 PUT 请求</h4> 20 <form action="/user/rest/1" method="POST"> 21 <input type="hidden" name="_method" value="PUT" /> 22 <p><input type="submit" value="PUT" /></p> 23 </form> 24 25 <hr /> 26 <h4>发送 DELETE 请求</h4> 27 <form action="/user/rest/1" method="POST"> 28 <input type="hidden" name="_method" value="DELETE" /> 29 </p><input type="submit" value="DELETE" /></p> 30 </form> 31 32 </body> 33 </html>
(2) 在 web.xml 文件中添加以下代码
1 <!-- 将 POST 请求转化为 PUT 请求和 DELETE 请求 --> 2 <filter> 3 <filter-name>hiddenHttpMethodFilter</filter-name> 4 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> 5 </filter> 6 <filter-mapping> 7 <filter-name>hiddenHttpMethodFilter</filter-name> 8 <url-pattern>/*</url-pattern> 9 </filter-mapping>
(3) 创建 src/main/java/com/example/controller/UserController.java 文件
1 package com.example.controller; 2 3 import org.springframework.ui.Model; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.PathVariable; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RequestMethod; 8 9 @Controller 10 @RequestMapping("/user") 11 public class UserController { 12 @RequestMapping("/rest") 13 public String rest() { 14 return "rest"; 15 } 16 17 @RequestMapping(value = "/rest/{id}", method = RequestMethod.GET) 18 public String restGet(@PathVariable Integer id, Model model) { 19 System.out.println("UserController -> restGet(): id = " + id); 20 model.addAttribute("message", "GET " + id); 21 return "success"; 22 } 23 24 @RequestMapping(value = "/rest/{id}", method = RequestMethod.POST) 25 public String restPost(@PathVariable Integer id, Model model) { 26 System.out.println("UserController -> restPost(): id = " + id); 27 model.addAttribute("message", "POST " + id); 28 return "success"; 29 } 30 31 @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT) 32 public String restPut(@PathVariable Integer id, Model model) { 33 System.out.println("UserController -> restPut(): id = " + id); 34 model.addAttribute("message", "PUT " + id); 35 return "success"; 36 } 37 38 @RequestMapping(value = "/rest/{id}", method = RequestMethod.DELETE) 39 public String restDelete(@PathVariable Integer id, Model model) { 40 System.out.println("UserController -> restDelete(): id = " + id); 41 model.addAttribute("message", "DELETE " + id); 42 return "success"; 43 } 44 45 }
访问:http://localhost:9090/user/rest
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战