Spring MVC是一種常用的Java Web框架,它提供了一種基於MVC模式的開發方式,可以方便地實現Web應用程式。在Spring MVC中,WebMvcConfigurer是一種常用的設定方式,可以允許我們自定義Spring MVC的行為,比如新增攔截器、訊息轉換器等。在本文中,我們將介紹什麼是WebMvcConfigurer,以及如何使用它來自定義Spring MVC的設定。可以看到WebMvcConfigurer是一個非常靈活和強大的工具,它可以讓我們實現自己的業務需求並提高程式碼的可讀性和可維護性。而且我們在Spring、SpringBoot都可以很簡單的使用WebMvcConfigurer,下面主要在SpringBoot中說明設定。
WebMvcConfigurer和WebMvcConfigurationSupport都是Spring MVC中的元件,它們都可以用於設定Spring MVC的一些特性。 具體的區別如下: ①:實現方式不同: WebMvcConfigurer: 是一個介面,它提供了多個回撥方法,可以用於自定義Spring MVC的設定(如訊息轉換器、攔截器等)。我們在使用時只需要實現 該介面,重寫其中的方法即可。 WebMvcConfigurationSupport: 是一個抽象類,它也提供了多個回撥方法,用於自定義Spring MVC的設定,但是需要繼承該類並重寫其中的方法。 ②:作用不同: WebMvcConfigurer: 主要用於新增或修改Spring MVC的設定,如新增攔截器,自定義訊息轉換器等。 WebMvcConfigurationSupport: 主要用於完全自定義Spring MVC的設定,如果我們需要對Spring MVC的設定進行大量的自定義,可以選擇繼承該類並重寫其中的 方法。但是需要注意的是,繼承該類會覆蓋Spring MVC的部分預設設定。 因此,當我們只需要對部分設定進行自定義時,應該使用WebMvcConfigurer。 ③:繼承關係不同 WebMvcConfigurer: 沒有繼承關係,我們只需要實現該介面即可使用。 WebMvcConfigurationSupport: 是一個抽象類,需要繼承後才能使用。 總結:在日常開發中推薦優先使用WebMvcConfigurer的方式,因為簡單方便,也沒有特別複雜的客製化需求; 若我們專案中使用的MVC存在著更加複雜的設定需求推薦WebMvcConfigurationSupport,通過繼承此類,我們可以說對官方的MVC程式碼進行 重寫操作,但是因為其設定量較大,實現比較複雜,因此在日常開發中使用WebMvcConfigurationSupport並不常見。
說MVC設定,其實就是說在WebMvcConfigurer介面提供了很多種自定義設定,需要我們自定義設定,其常用設定如下:
1:addInterceptors(攔截器設定)
這個方法可用於設定攔截器。
2:addCorsMappings(全域性跨域處理)
這個方法用來設定跨域存取的規則。
3:addViewControllers(註冊檢視控制器)
這個方法可以註冊一個或多個檢視控制器,讓我們寫的地址可以對應一個資原始檔,如html檔案
4:addResourceHandlers(設定靜態資源處理)
方法可用於設定靜態資源處理器。可以在使用者端直接存取靜態資源資訊
Spring中設定WebMvcConfigurer方式: ①:建立一個 Java 類,並實現WebMvcConfigurer介面 @Configuration public class MyWebMvcConfig implements WebMvcConfigurer { // 自定義設定程式碼 } ②:注入到Bean容器裡 @Configuration public class AppConfig { @Bean public MyWebMvcConfig myWebMvcConfig() { return new MyWebMvcConfig(); } } SpringBoot中設定WebMvcConfigurer方式:(這種方式簡單) ①:建立一個 Java 類,並實現WebMvcConfigurer介面就可以了 @Configuration public class MyWebMvcConfig implements WebMvcConfigurer { // 自定義設定程式碼 }
在SpringBoot中,我們可以使用攔截器來對請求進行統一的預處理或後處理。攔截器可以用於紀錄檔記錄、許可權檢查、效能監控、事務控制等方面,是一個非常重要的元件。要在SpringBoot中實現攔截器,則首先要建立一個實現HandlerInterceptor介面的攔截器類。該介面定義了三個方法,分別是preHandle、postHandle和afterCompletion,用於在請求處理前、請求處理後和請求完成後進行處理。
HandlerInterceptor介面方法詳解:
①:preHandler
在請求處理之前被呼叫。該方法在Interceptor類中最先執行,用來進行一些前置初始化操作或是對當前請求做預處理,
也可以進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是Boolean型別,當它返回false時,表示請求結束,
後續的Interceptor和Controller都不會再執行;當它返回為true時會繼續呼叫下一個Interceptor的preHandle方法,
如果已經是最後一個Interceptor的時候就會呼叫當前請求的Controller方法。
②:postHandler
在請求處理完成之後呼叫。也就是Controller方法呼叫之後執行,但是它會在DispatcherServlet進行檢視返回渲染之前被呼叫,
所以我們可以在這個方法中對Controller處理之後的ModelAndView物件進行操作。
③:afterCompletion
在整個請求結束後呼叫。就是對應的Interceptor類的postHandler方法返回true時才執行。就是說該方法將在整個請求結束之後,
也就是在DispatcherServlet渲染了對應的檢視之後執行。此方法主要用來進行資源清理。
注:官方其實不建議我們非要把3個方法都重寫,我們只要對需要的方法重寫介面,就比如大部分專案只需要重寫preHandler方法
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 這裡我自定義了一個攔截器的攔截規範,後面需要設定到WebMvcConfigurer * 我們可以設定多個攔截器,到時候全部設定到WebMvcConfigurer裡 * 注:這裡我定義的攔截器就當許可權許可權路徑攔截(具體專案裡我們可以起一個見名知意的攔截器 如:PermissionInterceptor) * * @author Anhui OuYang * @version 1.0 **/ @Component // 載入Bean容器裡 public class MyInterceptor implements HandlerInterceptor { private final Logger log = LoggerFactory.getLogger(this.getClass()); /*** * 在請求處理前進行呼叫(Controller方法呼叫之前) * 可以在這一階段進行全域性許可權校驗等操作 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("【preHandle】在請求處理前進行呼叫,自定義攔截器被執行。。。。"); // 這下面我們可以進行許可權校驗,校驗token操作..... // ..... //上面的程式碼執行完,若可以放行本次請求則一定要返回true,這樣才會到達Controller return true; } /*** * 請求處理之後進行呼叫,但是在檢視被渲染之前(Controller方法呼叫之後) * 可以在這個階段來操作 ModelAndView */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("【postHandle】請求處理之後進行呼叫,自定義攔截器被執行。。。。"); } /*** * 在整個請求結束之後被呼叫,也就是在DispatcherServlet渲染了對應的檢視之後執行,主要用於資源清理工作。 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("【afterCompletion】在整個請求結束之後被呼叫,自定義攔截器被執行。。。。"); } }
我們設定完攔截器之後,這時攔截器只是一個未被註冊的普通類,這時需要把一個或者多個攔截器註冊到WebMvcConfigurer
InterceptorRegistry類方法介紹:
①:addInterceptor
該方法用於向攔截器鏈中新增一個攔截器。interceptor引數為待新增的攔截器物件,可以是自定義的攔截器類或Spring提供的預置
攔截器。返回值為InterceptorRegistration物件,用於進一步設定該攔截器的屬性。
②:addWebRequestInterceptor
該方法用於向WebRequest攔截器鏈中新增一個攔截器。interceptor引數為待新增的攔截器物件,
可以是自定義的WebRequestInterceptor類或者Spring提供的預置攔截器。
也是返回值為InterceptorRegistration物件,用於進一步設定該攔截器的屬性。
③:getInterceptors
用於獲取當前已經新增到攔截器鏈中的所有攔截器,返回值為List<HandlerInterceptor>物件,表示攔截器列表。
InterceptorRegistration類方法介紹:
①:order
該方法用於設定攔截器的執行順序,即在攔截器鏈中的位置。order引數為一個整數,值越小表示越先執行。
②:addPathPatterns
該方法用於設定需要攔截的請求路徑模式,即滿足哪些請求路徑時才會觸發該攔截器。若"/**"則攔截全部;
傳入的引數是一個字串陣列,包含多個Ant風格的路徑模式,例如 "/api/**"、"/user/*"等。
③:excludePathPatterns
該方法用於設定不需要攔截的請求路徑模式,即滿足哪些請求路徑時不會觸發該攔截器。一般不攔截,如登入或者Swagger等
傳入的引數是一個字串陣列,包含多個Ant風格的路徑模式,例如 "/api/login"、"/user/login"等。
④:pathMatcher
該方法用於設定該攔截器所使用的PathMatcher範例,從而可以自定義路徑匹配邏輯。
@Configuration // 一定要設定為設定類 @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { // 屬性注入(使用構造器注入,通過@RequiredArgsConstructor註解生成必要的構造器) private final MyInterceptor myInterceptor; /*** * 設定攔截器資訊 * @param registry 攔截器登入檔 */ @Override public void addInterceptors(InterceptorRegistry registry) { // 設定myInterceptor的攔截規範(如攔截的路徑等等) InterceptorRegistration interceptorA = registry.addInterceptor(myInterceptor); // 設定攔截器的設定規則 interceptorA // 指定攔截器的執行順序。值越小,越先執行攔截器(但是得整型)。 .order(1) // 設定需要攔截的路徑(這裡攔截所有的路徑) .addPathPatterns("/api/**", "/user/*", "/**") // 設定攔截器的放行資源(代表不攔截) // 設定登入放行 .excludePathPatterns("/login") // 設定Swagger存取放行 .excludePathPatterns("/swagger-ui.html/**", "/swagger-ui.html", "/swagger-ui.html#/**") // 如資原始檔放行 .excludePathPatterns("/doc.html", "classpath:/META-INF/resources/"); // 謹慎使用放行"/**",這代表全部放行了,那麼攔截器就相當於無效設定 //.excludePathPatterns("/**"); // 若有多個攔截器則在下面需要設定多個(如下面interceptorB,我們需要對這個進行路徑攔截的設定) // InterceptorRegistration interceptorB = registry.addInterceptor(自定義的攔截器物件); } }
注:攔截的路徑或者放行的路徑是以Controller開始的,如我們在application.yml設定的地址字首則不包含
如何是跨域,大家應該都瞭解,這裡我將在WebMvcConfigurer中解決跨域問題,其實跨域的解決方式很多,這裡我就簡單說明,具體參考:SpringBoot如何解決跨域的三種方式
CorsRegistry類方法介紹:
①:addMapping
該方法用於新增允許跨域存取的路徑,String型別,若存在多個路徑則需要在CorsRegistry裡設定多個
CorsRegistration類方法介紹:
CorsRegistration是CorsRegistry的輔助類,使用它可以對單個跨域請求進行更細粒度的設定。
①:allowedOrigins(低版本使用,但是現在高版本也支援)
設定允許跨域請求的來源URL。該方法接受多個引數,每個引數為一個允許的來源URL。或者設定"*"
②:allowedOriginPatterns(一般使用這種方式)
設定允許跨域請求的來源URL的模式。該方法接受多個引數,每個引數為一個允許的來源URL模式。或者設定"*"
③:allowCredentials
設定是否允許跨域請求攜帶憑證資訊。預設情況下,瀏覽器不會向跨域請求傳送Cookie等憑證資訊。
如果希望攜帶憑證資訊,則需要將allowCredentials方法設定為true。
④:allowedMethods
設定允許跨域請求的HTTP方法。該方法接受多個引數,每個引數為一種允許的HTTP請求方式。
⑤:allowedHeaders
設定允許請求攜帶的HTTP頭資訊。該方法接受多個引數,每個引數為一種允許的HTTP頭資訊。(放行哪些請求頭部資訊)
⑥:exposedHeaders
設定響應頭資訊,這些資訊允許使用者端存取。該方法接受多個引數,每個引數為一種允許的響應頭資訊。(暴露哪些響應頭資訊)
⑦:combine
將當前CorsRegistration物件與另一個CorsConfiguration物件合併,返回合併後的CorsConfiguration物件。
可以使用該方法將多個CorsRegistration物件的設定合併到同一個CorsConfiguration物件中。
⑧:maxAge
設定響應的快取時間,單位為秒,預設30分鐘。
例如,當設定maxAge為3600時,如果瀏覽器在一小時內再次向同一個目標URL傳送跨域請求,
就可以直接使用以前的預檢請求結果,而不需要再次進行預檢請求。maxAge屬性隻影響預檢請求的快取時間,
而不會影響正常的跨域請求,因此不會對實際的業務邏輯產生影響。此外,maxAge屬性的具體值需要根據實際情況進行調整,
過小的快取時間可能會導致頻繁的預檢請求,過大的快取時間可能會使跨域請求的控制權得不到及時更新,從而增加安全風險。
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>簡單跨域測試</title> </head> <body> <button id="bt">簡單跨域測試</button> <script> function ajaxRequest() { return new Promise((resolve, reject) => { let xmlHttp = new XMLHttpRequest(); xmlHttp.open('get', 'http://127.0.0.1:8881/test/demo/getTestA', true); xmlHttp.send(); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState === 4) { xmlHttp.status === 200 ? resolve(xmlHttp.responseText) : reject("error") } } }).then(resolve => { console.log(resolve) }, reject => { console.log(reject) }) } document.getElementById("bt").onclick = function () { console.log("點選按鈕,傳送請求"); ajaxRequest(); } </script> </body> </html>
@Configuration // 一定要設定為設定類 public class WebMvcConfig implements WebMvcConfigurer { /*** * 設定全域性跨域處理 * @param registry CORS跨域登入檔 */ @Override public void addCorsMappings(CorsRegistry registry) { // 設定全域性跨域資訊 registry // 新增對映路徑(凡是在addMapping設定的路徑則代表可以跨域存取) .addMapping("/demo/**") // 設定放行哪些域 SpringBoot2.4.4下低版本使用.allowedOrigins("*") //.allowedOrigins("*") .allowedOriginPatterns("*") // 是否允許跨域請求攜帶憑證Cookie傳送 .allowCredentials(true) // 放行哪些請求方式,也可以使用.allowedMethods("*")放行全部 .allowedMethods("GET", "POST") // 放行哪些請求頭部資訊 .allowedHeaders("*") // 暴露哪些響應頭部資訊 .exposedHeaders("*") // 設定響應的快取時間 .maxAge(1800); // 若存在多個跨域則可以設定多個registry;"/**"代表所以都不跨域,相當設定這個,其它都沒必要設定了 // registry.addMapping("/**").allowedOriginPatterns("*"); } }
addViewControllers方法是SpringMVC中WebMvcConfigurer介面定義的一個方法,用於註冊一個簡單的檢視控制器,以便將請求路徑對映到一個具體的檢視頁面。在一些簡單的場景下,我們可能只需要將某個請求路徑直接對映到一個固定的檢視頁面,而不需要進行額外的邏輯處理。此時,可以使用addViewControllers方法來快速註冊一個檢視控制器,並指定對應的請求路徑和檢視名稱即可。但是我們需要額外注意的是,我們在SpringBoot中處理檢視跳轉時最好整合Thymeleaf,因為它是SpringBoot指定認可的並且,Thymeleaf與SpringMVC協同工作,可以方便地實現快速開發Web應用程式。
在SpringBoot設定Thymeleaf的一些資訊: 匯入Thymeleaf座標: <!--匯入SpringBoot整合Thymeleaf啟動器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 在application.yml設定Thymeleaf設定資訊: spring: # 設定Thymeleaf模板(預設啟動會請求/templates/index.html) thymeleaf: cache: false # 是否有模板快取 prefix: classpath:/templates/ # 模板放置的位置 suffix: .html # 模板字尾 mode: HTML # 模板型別 encoding: UTF-8 # 模板編碼
設定完基本的資訊後我們就可以進行資源檢視的跳轉了,如下一些基本介紹:
ViewControllerRegistry類說明:
①:addViewController(String urlPath)
通過urlPath引數指定的請求URL路徑(例如"/home")註冊一個簡單的檢視控制器,
該方法返回一個ViewControllerRegistration物件,通過該物件可以設定相關屬性,如檢視名稱、請求方式等。
如:registry.addViewController("/login");
②:setOrder(int order)
設定當前檢視控制器的執行順序,當有多個檢視控制器針對同一請求路徑時,可以使用該方法進行優先順序排序。
預設情況下,不同的檢視控制器按照它們被註冊的順序執行。
③:addRedirectViewController(String urlPath, String redirectUrl)
註冊一個重定向檢視控制器,將urlPath請求路徑重定向到指定的重定向地址redirectUrl。
如:registry.addRedirectViewController("/toBaidu","https://www.baidu.com");
ViewControllerRegistration類說明:
①:setViewName(String viewName)
資源路徑的字首
②:setStatusCode(HttpStatus statusCode)
設定存取不存在資源的響應碼,如下常見的:
HttpStatus.BAD_REQUEST:請求引數錯誤或格式不正確,例如缺少必需引數、引數型別錯誤等。
HttpStatus.UNAUTHORIZED:未經授權存取,需要使用者登入或提供憑證。
HttpStatus.FORBIDDEN:已經授權但存取被禁止,通常意味著許可權不足或需要進行進一步身份驗證。
HttpStatus.NOT_FOUND:請求的資源不存在,通常使用自定義的404錯誤頁面進行提示。
HttpStatus.METHOD_NOT_ALLOWED:請求方式不支援,例如GET請求存取只支援POST的介面時會返回405錯誤。
HttpStatus.INTERNAL_SERVER_ERROR:伺服器內部錯誤,需要在後臺進行排查和修復。
範例:registry.addViewController("/**").setStatusCode(HttpStatus.NOT_FOUND);
說明:存取不存在的頁面我一律按照404處理,但是我們templates/error/404.html頁面需要存在
說明:按照我們之前設定的thymeleaf設定來說,預設根路徑為resources/templates,跳轉的資原始檔都是.html檔案
@Configuration // 一定要設定為設定類 @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { /*** * 註冊請求路徑轉換到資源路徑 * @param registry 控制器登入檔 */ @Override public void addViewControllers(ViewControllerRegistry registry) { // 設定資源路徑跳轉(登入或退出都跳轉到resources/templates/login.html) registry.addViewController("/toLogin").setViewName("login"); registry.addViewController("/toExit").setViewName("login"); // 設定路徑重定向(若存取) registry.addRedirectViewController("/toSearch","https://www.baidu.com"); // 設定響應碼資訊 registry.addViewController("/**").setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } }
addResourceHandlers方法是SpringMVC框架提供的一種設定靜態資源的方式,它可以將指定的資源路徑對映到一個或多個URL路徑上,並指定資源的快取策略、版本號以及是否允許目錄列表等選項。具體來說,addResourceHandlers方法需要傳入一個ResourceHandlerRegistry物件作為引數,然後在這個物件上呼叫addResourceHandler方法,來新增一個或多個處理器。
ResourceHandlerRegistry類方法介紹:
①:addResourceHandler
該方法用於指定靜態資源的URL路徑,支援Ant風格的萬用字元,如「/resources/**」表示匹配所有以「/resources/」開頭的請求。
ResourceHandlerRegistration類方法介紹:
①:addResourceLocations
該方法為靜態資源所在的物理路徑或URL。可以使用多個addResourceLocations方法指定多個路徑,如下例:
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/", "file:/opt/files/")
說明:
classpath:/static/表示在專案的Classpath下(即src/main/resources資料夾下)查詢static資料夾,
file:/opt/files/表示在系統中的/opt/files/目錄下查詢檔案。
②:setCacheControl
此方法用於設定快取控制頭(cache-control header),CacheControl是一個封裝了快取策略的類。例如:
CacheControl cc = CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic();
registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/static/")
.setCacheControl(cc);
說明:這將指示瀏覽器快取靜態資源30天,並且它們是public快取,意味著中間代理伺服器也可以快取資源。
③:setCachePeriod
該方法用於設定靜態資源快取時間,引數型別為Duration型別。如:
registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/static/")
.setCachePeriod(Duration.ofMinutes(5));
說明:靜態資源快取的過期時間為5分鐘
④:setOptimizeLocations
此方法用於啟用或禁用位置優化。如果啟用位置優化,則將優化靜態資源的位置,以便並行存取靜態資源時可以獲得更好的效能。
預設情況下,位置優化是禁用的。
⑤:setUseLastModified
此方法用於啟用或禁用上次修改時間檢查(last-modified check)。如果啟用上次修改時間檢查,則在每個請求中傳送一
個if-modified-since頭,以檢查是否需要返回新內容。預設情況下,上次修改時間檢查是啟用的。
⑥:resourceChain
用於開啟或關閉ResourceChain模式。當開啟ResourceChain模式時,每個資原始檔都會自動新增版本號,避免瀏覽器快取問題。
例如:registry.addResourceHandler("/resources/**").addResourceLocations("classpath:/static/")
.resourceChain(true);
請注意,要使ResourceChain生效,還需要設定addResolver
如:.resourceChain(false)
// 新增VersionResourceResolver,且指定版本號
.addResolver(new VersionResourceResolver()
.addFixedVersionStrategy("1.0.0", "/**"));
下次存取地址:http://localhost:8881/resources/1.0.0/xxx.css
@Configuration // 一定要設定為設定類 @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { /*** * 設定靜態存取資源(需要注意不要和addViewControllers衝突) * @param registry 資源處理程式登入檔 */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry // 指定靜態資源的URL路徑,當存取myStatic/**和yourStatic/**都會在static目錄裡找靜態資源 .addResourceHandler("/myStatic/**", "/yourStatic/**") .addResourceLocations("classpath:/static/") // 於設定快取控制頭(cache-control header)30天過期(30天內請求相同資源則不在傳送請求) .setCacheControl(CacheControl.maxAge(30, TimeUnit.DAYS).cachePublic()) .resourceChain(false) // 新增 VersionResourceResolver ,且指定版本號 .addResolver(new VersionResourceResolver() .addFixedVersionStrategy("1.0.0", "/**")); } }
其實還有許許多多的設定資訊,後面會慢慢補充。