1. 程式人生 > 其它 >Spring基礎知識(30)- Spring Boot (十一)

Spring基礎知識(30)- Spring Boot (十一)

定製 Spring MVC、攔截器(Interceptor)


1. 定製 Spring MVC

    Spring Boot 拋棄了傳統 xml 配置檔案,通過配置類(標註 @Configuration 的類,相當於一個 xml 配置檔案)以 JavaBean 形式進行相關配置。
    
    Spring Boot 對 Spring MVC 的自動配置可以滿足我們的大部分需求,但是我們也可以通過自定義配置類(標註 @Configuration 的類)並實現 WebMvcConfigurer 介面來定製 Spring MVC 配置,例如攔截器、格式化程式、檢視控制器等等。

    WebMvcConfigurer 是一個基於 Java  8 的介面,該介面定義了許多與 Spring MVC 相關的方法,其中大部分方法都是 default 型別的,且都是空實現。因此我們只需要定義一個配置類實現 WebMvcConfigurer 介面,並重寫相應的方法便可以定製 Spring MVC 的配置。

 

方法 描述
void configurePathMatch(PathMatchConfigurer configurer) HandlerMappings 路徑的匹配規則。
void configureContentNegotiation(ContentNegotiationConfigurer configurer) 內容協商策略(一個請求路徑返回多種資料格式)。
void configureAsyncSupport(AsyncSupportConfigurer configurer) 處理非同步請求。
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) 這個介面可以實現靜態檔案可以像 Servlet 一樣被訪問。
void addFormatters(FormatterRegistry registry) 新增格式化器或者轉化器。
void addInterceptors(InterceptorRegistry registry) 新增 Spring MVC 生命週期攔截器,對請求進行攔截處理。
void addResourceHandlers(ResourceHandlerRegistry registry) 新增或修改靜態資源(例如圖片,js,css 等)對映;
Spring Boot 預設設定的靜態資原始檔夾就是通過重寫該方法設定的。
void addCorsMappings(CorsRegistry registry) 處理跨域請求。
void addViewControllers(ViewControllerRegistry registry) 主要用於實現無業務邏輯跳轉,例如主頁跳轉,簡單的請求重定向,錯誤頁跳轉等
void configureViewResolvers(ViewResolverRegistry registry) 配置檢視解析器,將 Controller 返回的字串(檢視名稱),轉換為具體的檢視進行渲染。
void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) 新增解析器以支援自定義控制器方法引數型別,實現該方法不會覆蓋用於解析處理程式方法引數的內建支援;要自定義內建的引數解析支援, 同樣可以通過 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) 新增處理程式來支援自定義控制器方法返回值型別。使用此選項不會覆蓋處理返回值的內建支援;要自定義處理返回值的內建支援,請直接配置 RequestMappingHandlerAdapter。
void configureMessageConverters(List<HttpMessageConverter<?>> converters) 用於配置預設的訊息轉換器(轉換 HTTP 請求和響應)。
void extendMessageConverters(List<HttpMessageConverter<?>> converters) 直接新增訊息轉換器,會關閉預設的訊息轉換器列表;實現該方法即可在不關閉預設轉換器的起提下,新增一個自定義轉換器。
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) 配置異常解析器。
void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) 擴充套件或修改預設的異常解析器列表。

 
    在 Spring Boot 專案中,我們可以通過以下 2 中形式定製 Spring MVC:

        擴充套件 Spring MVC
        全面接管 Spring MVC


    1) 擴充套件 Spring MVC

        如果 Spring Boot 對 Spring MVC 的自動配置不能滿足我們的需要,還可以通過自定義一個 WebMvcConfigurer 型別(實現 WebMvcConfigurer 介面)的配置類(標註 @Configuration,但不標註 @EnableWebMvc 註解的類),來擴充套件 Spring MVC。
        
        這樣不但能夠保留 Spring Boot 對 Spring MVC 的自動配置,享受 Spring Boot 自動配置帶來的便利,還能額外增加自定義的 Spring MVC 配置。

        示例,在 “Spring基礎知識(27)- Spring Boot (八)” 裡 SpringbootWeb 專案 整合 Thymeleaf 的基礎上,程式碼如下。

            (1) 新增 jQuery (https://jquery.com/)

                下載 jQuery 3.6.0 放置到目錄 src/main/resources/static/js/jquery-3.6.0.min.js

            (2) 建立 src/main/resources/templates/home.html 檔案

 1                 <!DOCTYPE html>
 2                 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3                 <head>
 4                     <meta charset="UTF-8">
 5                     <title>Home</title>
 6                     <script language="javascript" src="/js/jquery-3.6.0.min.js"></script>
 7                 </head>
 8                 <body>
 9 
10                     <h3>Home Page</h3>
11                     <p>jQuery Status: <span id="jquery_status">OFF</span></p>
12 
13                     <script type="text/javascript">
14                         $(document).ready(function() {
15                             console.log("Home page");
16                             $("#jquery_status").html("ON");
17                         });
18                     </script>
19                 </body>
20                 </html>


            (3) 建立 src/main/java/com/example/config/ExtendMvcConfigurer.java 檔案

 1                 package com.example.config;
 2 
 3                 import org.springframework.context.annotation.Configuration;
 4                 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 5                 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 6 
 7                 // 實現 WebMvcConfigurer 介面可以來擴充套件 SpringMVC 的功能
 8                 @Configuration
 9                 public class ExtendMvcConfigurer implements WebMvcConfigurer {
10 
11                     @Override
12                     public void addViewControllers(ViewControllerRegistry registry) {
13 
14                         // 當訪問 “/” 或 “/index.html” 時,都直接跳轉到 home
15                         registry.addViewController("/").setViewName("home");
16                         registry.addViewController("/index.html").setViewName("home");
17                     }
18 
19                 }


            (4) 建立 src/main/java/com/example/controller/IndexController.java 檔案

 1                 package com.example.controller;
 2 
 3                 import org.springframework.stereotype.Controller;
 4                 import org.springframework.web.bind.annotation.RequestMapping;
 5 
 6                 @Controller
 7                 public class IndexController {
 8                     @RequestMapping("/home")
 9                     public String home() {
10                         return "home";
11                     }
12                 }


            訪問 http://localhost:9090/   

                Home Page

                jQuery Status: ON    


    2) 完全接管 Spring MVC

        在一些特殊情況下,可能需要拋棄 Spring Boot 對 Spring MVC 的全部自動配置,完全接管 Spring MVC。此時可以自定義一個 WebMvcConfigurer 型別(實現 WebMvcConfigurer 介面)的配置類,並在該類上標註 @EnableWebMvc 註解,來實現完全接管 Spring MVC。

            注:完全接管 Spring MVC 後,Spring Boot 對 Spring MVC 的自動配置將全部失效。
        
        示例,在上文 SpringbootWeb 專案基礎上,程式碼如下。

            修改 src/main/java/com/example/config/ExtendMvcConfigurer.java 檔案

 1                 package com.example.config;
 2 
 3                 import org.springframework.context.annotation.Configuration;
 4                 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 5                 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
 6                 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 7 
 8                 // 實現 WebMvcConfigurer 介面可以來擴充套件 SpringMVC 的功能
 9                 @EnableWebMvc  // 完全接管SpringMVC
10                 @Configuration
11                 public class ExtendMvcConfigurer implements WebMvcConfigurer {
12 
13                     @Override
14                     public void addViewControllers(ViewControllerRegistry registry) {
15 
16                         // 當訪問 “/” 或 “/index.html” 時,都直接跳轉到 home
17                         registry.addViewController("/").setViewName("home");
18                         registry.addViewController("/index.html").setViewName("home");
19                     }
20 
21                 }


            訪問 http://localhost:9090/      

                Home Page

                jQuery Status: OFF

            注:Spring Boot 能夠訪問位於靜態資原始檔夾中的靜態檔案,這是在 Spring Boot 對 Spring MVC 的預設自動配置中定義的。@EnableWebMvc 完全接管 Spring MVC 後,Spring Boot 對 Spring MVC 的預設配置都會失效,此時再訪問靜態資原始檔夾中的靜態資源(js/jquery-3.6.0.min.js)就會報 404 錯誤。


2. 攔截器(Interceptor)

    對於攔截器我們並不陌生,無論是 Struts 2 還是 Spring MVC 中都提供了攔截器功能,它可以根據 URL 對請求進行攔截,主要應用於登陸校驗、許可權驗證、亂碼解決、效能監控和異常處理等功能上。Spring Boot 同樣提供了攔截器功能。

    在 Spring Boot 專案中,使用攔截器功能通常需要以下步驟:

        (1) 定義攔截器;
        (2) 註冊攔截器;
        (3) 指定攔截規則(如果是攔截所有,靜態資源也會被攔截)。


    1) 定義攔截器

        在 Spring Boot 中定義攔截器十分的簡單,只需要建立一個攔截器類,並實現 HandlerInterceptor 介面即可。

        HandlerInterceptor  介面中定義以下 3 個方法,如下表。

方法 描述
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 該方法在控制器處理請求方法前執行,其返回值表示是否中斷後續操作,返回 true 表示繼續向下執行,返回 false 表示中斷後續操作。
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)  該方法在控制器處理請求方法呼叫之後、解析檢視之前執行,可以通過此方法對請求域中的模型和檢視做進一步修改。
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 該方法在檢視渲染結束後執行,可以通過此方法實現資源清理、記錄日誌資訊等工作。


        實現 HandlerInterceptor 介面,程式碼如下:

 1             public class LoginInterceptor implements HandlerInterceptor {
 2 
 3                 @Override
 4                 public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
 5                                         Object handler) throws Exception {
 6 
 7                 }
 8 
 9                 @Override
10                 public void postHandle(HttpServletRequest request, HttpServletResponse response,
11                                         Object handler, ModelAndView modelAndView) throws Exception {
12 
13                 }
14 
15                 @Override
16                 public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
17                                             Object handler, Exception ex) throws Exception {
18 
19                 }
20             } 

 

    2) 註冊攔截器

        建立一個實現了 WebMvcConfigurer 介面的配置類(使用了 @Configuration 註解的類),重寫 addInterceptors() 方法,並在該方法中呼叫 registry.addInterceptor() 方法將自定義的攔截器註冊到容器中。

        實現 WebMvcConfigurer 介面,程式碼如下:

 1             // 實現 WebMvcConfigurer 介面可以來擴充套件 SpringMVC 的功能
 2             @Configuration
 3             public class ExtendMvcConfig implements WebMvcConfigurer {
 4                 ...
 5 
 6                 @Override
 7                 public void addInterceptors(InterceptorRegistry registry) {
 8                     registry.addInterceptor(new LoginInterceptor());
 9                 }
10             }

 

    3) 指定攔截規則

        修改 ExtendMvcConfig 配置類中 addInterceptors() 方法的程式碼,繼續指定攔截器的攔截規則,程式碼如下。

 1         @Configuration
 2         public class ExtendMvcConfig implements WebMvcConfigurer {
 3             ...
 4 
 5             @Override
 6             public void addInterceptors(InterceptorRegistry registry) {
 7                 registry.addInterceptor(new LoginInterceptor())
 8                         .addPathPatterns("/**") // 攔截所有請求,包括靜態資原始檔
 9                         .excludePathPatterns("/", "/index.html", "/user/login", "/user/login/post", "/css/**",
10                             "/images/**", "/js/**"); // 放行登入頁,登陸操作,靜態資源
11             }
12         }


        在指定攔截器攔截規則時,呼叫了兩個方法,這兩個方法的說明如下:

            addPathPatterns:該方法用於指定攔截路徑,例如攔截路徑為 “/**”,表示攔截所有請求,包括對靜態資源的請求。
            excludePathPatterns:該方法用於排除攔截路徑,即指定不需要被攔截器攔截的請求。

    示例,在 “Spring基礎知識(27)- Spring Boot (八)” 裡 SpringbootWeb 專案 整合 Thymeleaf 的基礎上,程式碼如下。

        (1) 建立 src/main/resources/templates/login.html 檔案

 1             <!DOCTYPE html>
 2             <html lang="en" xmlns:th="http://www.thymeleaf.org">
 3             <head>
 4                 <meta charset="UTF-8">
 5                 <title>Login</title>
 6             </head>
 7             <body>
 8 
 9                 <h3>Login</h3>
10 
11                 <p th:text="${msg}"></p>
12 
13                 <form action="/user/login/post" method="POST">
14 
15                     <p>Username: <input type="text" name="username" value="admin" /></p>
16                     <p>Password: <input type="text" name="password" value="123456" /></p>
17 
18                     <p><input type="submit" value="Submit" /></p>
19 
20                 </form>
21 
22             </body>
23             </html>


        (2) 建立 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 org.springframework.web.servlet.HandlerInterceptor;
 6             import org.springframework.web.servlet.ModelAndView;
 7 
 8             public class LoginInterceptor implements HandlerInterceptor {
 9 
10                 @Override
11                 public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
12                                         Object handler) throws Exception {
13                     Object loginUser = request.getSession().getAttribute("loginUser");
14                     if (loginUser == null) {
15                         request.setAttribute("msg", "No authentication, try login");
16                         request.getRequestDispatcher("/index.html").forward(request, response);
17                         return false;
18                     } else {
19                         return true;
20                     }
21                 }
22 
23                 @Override
24                 public void postHandle(HttpServletRequest request, HttpServletResponse response,
25                                     Object handler, ModelAndView modelAndView) throws Exception {
26                     System.out.println("LoginInterceptor -> postHandle(): modelAndView = " + modelAndView);
27                 }
28 
29                 @Override
30                 public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
31                                             Object handler, Exception ex) throws Exception {
32                     System.out.println("LoginInterceptor -> afterCompletion()");
33                 }
34             }


        (3) 建立 src/main/java/com/example/config/ExtendMvcConfigurer.java 檔案

 1             package com.example.config;
 2 
 3             import org.springframework.context.annotation.Configuration;
 4             import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 5             import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 6 
 7             // 實現 WebMvcConfigurer 介面可以來擴充套件 SpringMVC 的功能
 8             @Configuration
 9             public class ExtendMvcConfigurer implements WebMvcConfigurer {
10 
11                 @Override
12                 public void addViewControllers(InterceptorRegistry registry) {
13 
14                     // 當訪問 “/” 或 “/index.html” 時,都直接跳轉到登陸頁面
15                     registry.addViewController("/").setViewName("login");
16                     registry.addViewController("/index.html").setViewName("login");
17                     registry.addViewController("/home.html").setViewName("home");
18                 }
19 
20                 @Override
21                 public void addInterceptors(InterceptorRegistry registry) {
22 
23                     registry.addInterceptor(new LoginInterceptor())
24                             .addPathPatterns("/**") // 攔截所有請求,包括靜態資原始檔
25                             .excludePathPatterns("/", "/index.html", "/user/login", "/user/login/post",
26                                     "/css/**", "/images/**", "/js/**"); // 放行登入頁,登陸操作,靜態資源
27                 }                
28 
29             }


        (4) 建立 src/main/java/com/example/controller/UserController.java 檔案

 1             package com.example.controller;
 2 
 3             import java.util.Map;
 4             import javax.servlet.http.HttpSession;
 5             import org.springframework.stereotype.Controller;
 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 
13                 @RequestMapping("/login")
14                 public String login() {
15                     return "login";
16                 }
17 
18                 @RequestMapping(value = "/login/post")
19                 public String loginPost(String username, String password, 
20                                         Map<String, Object> map, HttpSession session) {
21 
22                     if ("admin".equals(username) && "123456".equals(password)) {
23                         session.setAttribute("loginUser", username);
24                         return "redirect:/home.html";
25                     } else {
26                         map.put("msg", "Invalid username or password");
27                     }
28 
29                     return "login";
30                 }
31             }


        訪問 http://localhost:9090/ 或 http://localhost:9090/home.html