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