Spring Boot 拋棄了傳統 xml 配置文件,通過配置類(標注 @Configuration 的類,相當於一個 xml 配置文件)以 JavaBean 形式進行相關配置。
Spring Boot 對 Spring MVC 的自動配置可以滿足我們的大部分需求,但是我們也可以通過自定義配置類(標注 @Configuration 的類)並實現 WebMvcConfigurer 接口來定制 Spring MVC 配置,例如攔截器、格式化程序、視圖控制器等等。
SpringBoot 1.5 及以前是通過繼承 WebMvcConfigurerAdapter 抽象類來定制 Spring MVC 配置的,但在 SpringBoot 2.0 后,WebMvcConfigurerAdapter 抽象類就被棄用了,改為實現 WebMvcConfigurer 接口來定制 Spring MVC 配置。
WebMvcConfigurer接口
WebMvcConfigurer 是一個基於 Java 8 的接口,該接口定義了許多與 Spring MVC 相關的方法,其中大部分方法都是 default 類型的,且都是空實現。因此我們只需要定義一個配置類實現 WebMvcConfigurer 接口,並重寫相應的方法便可以定制 Spring MVC 的配置。
//HandlerMappings 路徑的匹配規則。
default void configurePathMatch(PathMatchConfigurer configurer) {}
//內容協商策略(一個請求路徑返回多種數據格式)。
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
//處理異步請求。
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
//這個接口可以實現靜態文件可以像 Servlet 一樣被訪問。
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
//添加格式化器或者轉化器。
default void addFormatters(FormatterRegistry registry) {}
//添加 Spring MVC 生命周期攔截器,對請求進行攔截處理。
default void addInterceptors(InterceptorRegistry registry) {}
//添加或修改靜態資源(例如圖片,js,css 等)映射;
//Spring Boot 默認設置的靜態資源文件夾就是通過重寫該方法設置的。
default void addResourceHandlers(ResourceHandlerRegistry registry) {}
//處理跨域請求。
default void addCorsMappings(CorsRegistry registry) {}
//主要用於實現無業務邏輯跳轉,例如主頁跳轉,簡單的請求重定向,錯誤頁跳轉等
default void addViewControllers(ViewControllerRegistry registry) {}
//配置視圖解析器,將 Controller 返回的字符串(視圖名稱),轉換為具體的視圖進行渲染。
default void configureViewResolvers(ViewResolverRegistry registry) {}
//添加解析器以支持自定義控制器方法參數類型,實現該方法不會覆蓋用於解析處理程序方法參數的內置支持;
//要自定義內置的參數解析支持, 同樣可以通過 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
//添加處理程序來支持自定義控制器方法返回值類型。使用此選項不會覆蓋處理返回值的內置支持;
//要自定義處理返回值的內置支持,請直接配置 RequestMappingHandlerAdapter。
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
//用於配置默認的消息轉換器(轉換 HTTP 請求和響應)。
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
//直接添加消息轉換器,會關閉默認的消息轉換器列表;
//實現該方法即可在不關閉默認轉換器的起提下,新增一個自定義轉換器。
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
//配置異常解析器。
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
//擴展或修改默認的異常解析器列表。
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
在 Spring Boot 項目中,我們可以通過以下 2種形式定制 Spring MVC:
- 擴展 Spring MVC
- 全面接管 Spring MVC
擴展 Spring MVC
如果 Spring Boot 對 Spring MVC 的自動配置不能滿足我們的需要,我們還可以通過自定義一個 WebMvcConfigurer 類型(實現 WebMvcConfigurer 接口)的配置類(標注 @Configuration,但不標注 @EnableWebMvc 注解的類),來擴展 Spring MVC。這樣不但能夠保留 Spring Boot 對 Spring MVC 的自動配置,享受 Spring Boot 自動配置帶來的便利,還能額外增加自定義的 Spring MVC 配置。
全面接管 Spring MVC
在一些特殊情況下,我們可能需要拋棄 Spring Boot 對 Spring MVC 的全部自動配置,完全接管 Spring MVC。此時我們可以自定義一個 WebMvcConfigurer 類型(實現 WebMvcConfigurer 接口)的配置類,並在該類上標注 @EnableWebMvc 注解,來實現完全接管 Spring MVC。
注意:完全接管 Spring MVC 后,Spring Boot 對 Spring MVC 的自動配置將全部失效。
configurePathMatch
HandlerMappings 路徑的匹配規則。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void configurePathMatch(PathMatchConfigurer configurer) {
// 是否存在尾\來進行匹配 /user和/user/等效的,同樣可以進行匹配
configurer.setUseTrailingSlashMatch(true);
// 這個配置需要傳入一個UrlPathHelper對象,UrlPathHelper是一個處理url地址的幫助類,
// 他里面有一些優化url的方法
// 比如:getSanitizedPath,就是將// 換成/ 所以我們在輸入地址欄的時候,//也是沒有問題的,
// 這里使用springmvc默認的就可以了,如果想要深入了解,那么我們后續在深入
UrlPathHelper urlPathHelper = new UrlPathHelper();
configurer.setUrlPathHelper(urlPathHelper);
// 路徑匹配器 PathMatcher是一個接口,springmvc默認使用的是AntPathMatcher
// 這里也就不深入了,使用springmvc默認的就可以,如果想要深入了解,那么我們后續在深入,查看AntPathMatcher的源碼
// configurer.setPathMatcher();
// 配置路徑前綴
// 下面這樣寫的意思是:對含有AdminController注解的controller添加/admin地址前綴
// configurer.addPathPrefix("admin", c -> c.isAnnotationPresent(AdminController.class));
// configurer.addPathPrefix("app", c -> c.isAnnotationPresent(AppController.class));
// 當然這里也不一定非得用注解來實現:也可以用分包:
configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.harvey.tim.web.controller"));
// configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.admin"));
// configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.app"));
}
}
configureContentNegotiation
內容協商策略(一個請求路徑返回多種數據格式)。
內容協商機制這個太專業的名稱,頭一次聽的話,估計是無法理解它其中的含義的。
簡單點說,就是客戶端向服務端發送一個請求,然后服務端給客戶端返回什么格式的數據的,是需要兩端進行協商的,既然是協商,那么它們有什么協議或者規則呢?
一般現在服務端返回的數據基本都是json格式的數據,以前返回的是xml,那么現在如果要返回xml格式的數據,spring mvc也是提供有方法的。
@RequestMapping系列注解中produces可以指定返回得格式,(@GetMapping是@RequestMapping得一種變形)
寫法:
@GetMapping(value = “xxx”,produces = MediaType.APPLICATION_XML_VALUE)
注意:在返回的實體中指定@XmlRootElement(name = "xxx"),指定xml的根元素。
@GetMapping(value = "getUser",produces = MediaType.APPLICATION_XML_VALUE)
public User getUser(){
User user = new User();
user.setName("我是admin用戶");
return user;
}
返回實體:
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "user")
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
最后得到的結果就是:
<user>
<name>我是admin用戶</name>
</user>
但是這只是服務端指定的,客戶端不能夠決定,如果客戶端請求這個路徑需要換成json格式的數據,那么是做不到的,所以就有了內容協商機制,也就是可以相互協商,客戶端需要什么樣的格式,給個參數說明,服務端就可以返回客戶端需要的數據了。
在springmvc中,我們需要達到內容協商機制,那么就需要覆蓋WebMvcConfigurer中的configureContentNegotiation方法
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 是否通過請求參數來決定返回數據,默認為false,當false的時候,只能是@RestController默認的json格式
configurer.favorParameter(true)
//這個方法過期了,這里不討論
//.favorPathExtension(true)
.ignoreAcceptHeader(true) // 不檢查Accept請求頭
.parameterName("zyMediaType")// 參數名稱,就是通過什么樣的參數來獲取返回值
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml",MediaType.APPLICATION_XML);
// admin/getUser?zyMediaType=xml 時返回結果為xml格式
// admin/getUser?zyMediaType=json 時返回結果為json格式
}
}
我們再創建一個控制器:(注意,返回的實體中,在類上面必須打上@XmlRootElement注解,不然在請求xml格式的時候會報錯):
@GetMapping(value = "testMediaType")
public User testMediaType(){
User user = new User();
user.setName("testMediaType");
return user;
}
configureAsyncSupport
處理異步請求。
對異步處理調優的一些參數配置,Spring默認異步線程是不使用線程池的,可以自己設定一些可以重用的線程。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
//注冊callable攔截器
configurer.registerCallableInterceptors();
//注冊deferredResult攔截器
configurer.registerDeferredResultInterceptors();
//異步請求超時時間
configurer.setDefaultTimeout(10000);
//設定異步請求線程池callable等, spring默認線程不可重用
configurer.setTaskExecutor(taskExecutor());
}
private AsyncTaskExecutor taskExecutor(){
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("system");
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(50);
threadPoolTaskExecutor.setKeepAliveSeconds(20);
//LinkedBlockingQueue
threadPoolTaskExecutor.setQueueCapacity(1000);
threadPoolTaskExecutor.setPrestartAllCoreThreads(true);
threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
return threadPoolTaskExecutor;
}
}
configureDefaultServletHandling
實現靜態文件可以像 Servlet 一樣被訪問(一般也不會用)。
/**
* 將請求的靜態資源經由spring mvc交回web容器本身默認的servlet去處理,
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
addFormatters
添加格式化器或者轉化器。
Formatter繼承樹如下:

可以發現整個繼承關系並不復雜,甚至可以說非常簡單。只有一個抽象子類,AbstractNumberFormatter,這個類抽象了對數字進行格式化時的一些方法,它有三個子類,分別處理不同的數字類型,包括貨幣,百分數,正常數字。其余的子類都是直接實現了Formatter接口。其中我們比較熟悉的可能就是DateFormatter了。
addInterceptors
添加 Spring MVC 生命周期攔截器,對請求進行攔截處理。
我們在項目中,有時候需要監聽一下從發出請求到后台處理渲染頁面完成這一時間段內的生命周期,並根據實際業務加以限制,比如比較常用的就是攔截所以驗證是否登錄。
那么springmvc給我們提供了添加自定義生命周期攔截器的配置,那就是addInterceptors。
addResourceHandlers
添加或修改靜態資源(例如圖片,js,css 等)映射;
Spring Boot 默認設置的靜態資源文件夾就是通過重寫該方法設置的。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 添加靜態資源路徑,然后跟項目的路徑進行映射
// 當訪問 http://localhost:8080/static/hello.html 時,會去類路徑的static/html目錄查找hello.html頁面
registry.addResourceHandler("/static/**","/**").addResourceLocations("classpath:/static/html/");
}
}
addCorsMappings
處理跨域請求。
跨域這個詞,在前后端分離項目中,應該是比較常見的一個詞語。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 添加請求映射,就是對哪些地址進行跨域處理
.allowedOrigins("*") // 特定來源的允許來源列表。這里配置*表示所以網站都可進行跨域,這里生產介意指定特定的地址
.allowedHeaders("*") // 允許請求頭攜帶的標題: "Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"
.allowCredentials(true) // 瀏覽器是否攜帶憑證
.allowedMethods("*") // 允許跨域的請求方式,可以進行指定:GET,POST
.maxAge(3600); // 客戶端緩存的時間,默認為1800(30分鍾)
}
}
addViewControllers
主要用於實現無業務邏輯跳轉,例如主頁跳轉,簡單的請求重定向,錯誤頁跳轉等
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 這里前提得有404和500這一個html存在,不然它會返回默認的404頁面
// 我們在static/error目錄下創建404.html和500.html
// 直接訪問 http://localhost/404 會去找 /static/error/404.html
// 默認是forward方式的, 地址欄鏈接是是不會變的
// forward方式:http://localhost/404
// redirect方式:http://localhost/error/404.html
// registry.addViewController("/500").setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR).setViewName("error/500.html");
// registry.addViewController("/404").setStatusCode(HttpStatus.NOT_FOUND).setViewName("error/404.html");
//注意:頁面跳轉需要考慮是否需要配合addResourceHandlers對靜態資源進行處理
registry.addViewController("/500").setViewName("redirect:/error/500.html");
registry.addViewController("/404").setViewName("redirect:/error/404.html");
}
}
configureViewResolvers
配置視圖解析器,將 Controller 返回的字符串(視圖名稱),轉換為具體的視圖進行渲染。
從方法名稱我們就能看出這個方法是用來配置視圖解析器的,該方法的參數ViewResolverRegistry 是一個注冊器,用來注冊你想自定義的視圖解析器等。ViewResolverRegistry 常用的幾個方法:
(1)enableContentNegotiation()
該方法會創建一個內容裁決解析器ContentNegotiatingViewResolver ,該解析器不進行具體視圖的解析,而是管理你注冊的所有視圖解析器,所有的視圖會先經過它進行解析,然后由它來決定具體使用哪個解析器進行解析。具體的映射規則是根據請求的media types來決定的。
(2)UrlBasedViewResolverRegistration()
public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
this.viewResolvers.add(resolver);
return new UrlBasedViewResolverRegistration(resolver);
}
方法會注冊一個內部資源視圖解析器InternalResourceViewResolver 顯然訪問的所有jsp都是它進行解析的。該方法參數用來指定路徑的前綴和文件后綴,如:registry.jsp("/WEB-INF/jsp/", ".jsp"); 對於以上配置,假如返回的視圖名稱是example,它會返回/WEB-INF/jsp/example.jsp給前端,找不到則報404。
(3)beanName()
public void beanName() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
this.viewResolvers.add(resolver);
}
該方法會注冊一個BeanNameViewResolver 視圖解析器,這個解析器是干嘛的呢?它主要是將視圖名稱解析成對應的bean。什么意思呢?假如返回的視圖名稱是example,它會到spring容器中找有沒有一個叫example的bean,並且這個bean是View.class類型的?如果有,返回這個bean。
(4)viewResolver()
public void viewResolver(ViewResolver viewResolver) {
if (viewResolver instanceof ContentNegotiatingViewResolver) {
throw new BeanInitializationException(
"addViewResolver cannot be used to configure a ContentNegotiatingViewResolver. Please use the method enableContentNegotiation instead.");
}
this.viewResolvers.add(viewResolver);
}
這個方法想必看名字就知道了,它就是用來注冊各種各樣的視圖解析器的,包括自己定義的。
addArgumentResolvers
添加解析器以支持自定義控制器方法參數類型,實現該方法不會覆蓋用於解析處理程序方法參數的內置支持;
要自定義內置的參數解析支持, 同樣可以通過 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter 。
addReturnValueHandlers
添加處理程序來支持自定義控制器方法返回值類型。使用此選項不會覆蓋處理返回值的內置支持;
要自定義處理返回值的內置支持,請直接配置 RequestMappingHandlerAdapter。
configureMessageConverters
用於配置默認的消息轉換器(轉換 HTTP 請求和響應)。
向列表中添加轉換器將關閉默認轉換器注冊,不覆蓋默認轉換器請使用extendMessageConverters。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//1.需要先定義一個 convert 轉換消息的對象;
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//2、添加fastJson 的配置信息,比如:是否要格式化返回的json數據;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 不忽略對象屬性中的null值
fastJsonConfig.setSerializerFeatures(
PrettyFormat,
WriteNullListAsEmpty,
WriteNullStringAsEmpty);
//3、在convert中添加配置信息.
fastConverter.setFastJsonConfig(fastJsonConfig);
//4、將convert添加到converters當中.
converters.add(fastConverter);
}
}
extendMessageConverters
直接添加消息轉換器,會關閉默認的消息轉換器列表;
實現該方法即可在不關閉默認轉換器的起提下,新增一個自定義轉換器。
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//1.需要先定義一個 convert 轉換消息的對象;
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//2、添加fastJson 的配置信息,比如:是否要格式化返回的json數據;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 不忽略對象屬性中的null值
fastJsonConfig.setSerializerFeatures(
PrettyFormat,
WriteNullListAsEmpty,
WriteNullStringAsEmpty);
//3、在convert中添加配置信息.
fastConverter.setFastJsonConfig(fastJsonConfig);
//4、將convert添加到converters當中.
converters.add(fastConverter);
// converters.add(0, fastConverter);
}
configureHandlerExceptionResolvers
配置異常解析器。
HandlerExceptionResolver顧名思義,就是處理異常的類,接口就一個方法,出現異常之后的回調,四個參數中還攜帶了異常堆棧信息:
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
自定義全局異常:
@Slf4j
@Component
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
log.info("系統發生異常");
// 統一處理異常
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", "系統發生異常,請稍后重試");
modelAndView.setViewName("/error/500.html");
return modelAndView;
}
}
添加自定義異常:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new GlobalException());
}
}
extendHandlerExceptionResolvers
擴展或修改默認的異常解析器列表。
