1. 程式人生 > 其它 >Spring基礎知識(18)- Spring MVC (八)

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>&nbsp;</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