本篇文章內容詳細可參考官方文檔第 29 節。html
SpringBoot 很是適合 Web 應用程序開發。可使用嵌入式 Tomcat,Jetty,Undertow 或 Netty 建立自包含的 HTTP 服務器。大多數 Web 應用程序能夠經過使用 spring-boot-starter-web 模塊快速啓動和運行。你還能夠選擇使用該 spring-boot-starter-webflux 模塊構建響應式 Web 應用程序 。java
SpringMVC 框架是一個豐富的「模型視圖控制器」 Web框架。SpringMVC 能夠經過使用 @Controller 或 @RestController 註解來標註控制器處理傳入的 HTTP 請求。控制器中的方法經過使用 @RequestMapping 註解完成請求映射 。web
SpringMVC 是 Spring Framework 的一部分,詳細信息可參考官方文檔,也可參考博客。spring
SpringMVC 的自動配置類爲 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration 。spring-mvc
SpringBoot 爲 SpringMVC 自動配置了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 即視圖解析器。springboot
查看 BeanNameViewResolver 配置:服務器
1 @Bean 2 @ConditionalOnBean(View.class) 3 @ConditionalOnMissingBean 4 public BeanNameViewResolver beanNameViewResolver() { 5 BeanNameViewResolver resolver = new BeanNameViewResolver(); 6 resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); 7 return resolver; 8 }
查看 BeanNameViewResolver 的 resolveViewName 方法:mvc
1 @Override 2 public View resolveViewName(String viewName, Locale locale) throws BeansException { 3 ApplicationContext context = getApplicationContext(); 4 if (!context.containsBean(viewName)) { 5 if (logger.isDebugEnabled()) { 6 logger.debug("No matching bean found for view name '" + viewName + "'"); 7 } 8 // Allow for ViewResolver chaining... 9 return null; 10 } 11 if (!context.isTypeMatch(viewName, View.class)) { 12 if (logger.isDebugEnabled()) { 13 logger.debug("Found matching bean for view name '" + viewName + 14 "' - to be ignored since it does not implement View"); 15 } 16 // Since we're looking into the general ApplicationContext here, 17 // let's accept this as a non-match and allow for chaining as well... 18 return null; 19 } 20 return context.getBean(viewName, View.class); 21 }
能夠看到,該方法就是在容器中查看是否包含對應名稱的 View 實例,若是包含,則直接返回。app
查看 ContentNegotiatingViewResolver 配置:框架
@Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) { ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver(); resolver.setContentNegotiationManager( beanFactory.getBean(ContentNegotiationManager.class)); resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; }
查看 ContentNegotiatingViewResolver 的 resolveViewName 方法:
1 @Override 2 public View resolveViewName(String viewName, Locale locale) throws Exception { 3 RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); 4 Assert.isInstanceOf(ServletRequestAttributes.class, attrs); 5 List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); 6 if (requestedMediaTypes != null) { 7 List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); 8 View bestView = getBestView(candidateViews, requestedMediaTypes, attrs); 9 if (bestView != null) { 10 return bestView; 11 } 12 } 13 if (this.useNotAcceptableStatusCode) { 14 if (logger.isDebugEnabled()) { 15 logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code"); 16 } 17 return NOT_ACCEPTABLE_VIEW; 18 } 19 else { 20 logger.debug("No acceptable view found; returning null"); 21 return null; 22 } 23 }
看到第 7 行經過 getCandidateViews(viewName, locale, requestedMediaTypes) 方法獲取候選 View 的列表,查看該方法:
1 private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) 2 throws Exception { 3 4 List<View> candidateViews = new ArrayList<View>(); 5 for (ViewResolver viewResolver : this.viewResolvers) { 6 View view = viewResolver.resolveViewName(viewName, locale); 7 if (view != null) { 8 candidateViews.add(view); 9 } 10 for (MediaType requestedMediaType : requestedMediaTypes) { 11 List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType); 12 for (String extension : extensions) { 13 String viewNameWithExtension = viewName + "." + extension; 14 view = viewResolver.resolveViewName(viewNameWithExtension, locale); 15 if (view != null) { 16 candidateViews.add(view); 17 } 18 } 19 } 20 } 21 if (!CollectionUtils.isEmpty(this.defaultViews)) { 22 candidateViews.addAll(this.defaultViews); 23 } 24 return candidateViews; 25 }
能夠看到該方法實際上是在遍歷 this.viewResolvers 來解析獲取全部候選的 View,最後返回。那這個 this.viewResolvers 是什麼呢?查看該解析器的初始化方法:
1 @Override 2 protected void initServletContext(ServletContext servletContext) { 3 Collection<ViewResolver> matchingBeans = 4 BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values(); 5 if (this.viewResolvers == null) { 6 this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.size()); 7 for (ViewResolver viewResolver : matchingBeans) { 8 if (this != viewResolver) { 9 this.viewResolvers.add(viewResolver); 10 } 11 } 12 } 13 else { 14 for (int i = 0; i < viewResolvers.size(); i++) { 15 if (matchingBeans.contains(viewResolvers.get(i))) { 16 continue; 17 } 18 String name = viewResolvers.get(i).getClass().getName() + i; 19 getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name); 20 } 21 22 } 23 if (this.viewResolvers.isEmpty()) { 24 logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " + 25 "'viewResolvers' property on the ContentNegotiatingViewResolver"); 26 } 27 AnnotationAwareOrderComparator.sort(this.viewResolvers); 28 this.cnmFactoryBean.setServletContext(servletContext); 29 }
這個方法其實是經過 3-4 行從容器中獲取了全部 ViewResolver.class 類型 bean 的集合,即從容器中獲取了全部的視圖解析器。接着返回到 org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName 方法,在 8-10 行獲取到最合適的 View 返回。
總的來講,這個視圖解析器的做用就是從容器中拿到全部其它的視圖解析器解析出 View ,最終從這些 View 中挑選一個最合適的返回。因此咱們若是要本身定義視圖解析器,只須要將自定義的視圖解析器註冊到容器便可。
上一節中有提到,參考【靜態資源映射】。
上一節中有提到,參考【歡迎頁】。
上一節中有提到,參考【頁面圖標】。
SpringBoot 爲 SpringMVC 自動配置了轉換器(Converter)和格式化器(Formater)。例如:客戶端發來一個請求攜帶一些參數,這些參數要與請求方法上的入參進行綁定,進行綁定的過程當中涉及到類型轉換(如要將 "18" 轉換成 Integer 類型),這時就會用到轉換器。而若是這些參數中有些參數要轉換成日期或其它特殊類型,咱們要按約定將請求參數按指定方式格式化(如要將 "3.4.2018" 格式化成咱們須要的日期格式),這時就須要用到格式化器。轉換器與格式化器的註冊在自動配置類中也能夠看到:
1 @Bean 2 @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") 3 public Formatter<Date> dateFormatter() { 4 return new DateFormatter(this.mvcProperties.getDateFormat()); 5 }
能夠看到,SpringBoot 在配置類中註冊了一個日期格式化器,而這個日期格式化的規則能夠經過配置文件中的 spring.mvc.date-format 屬性進行配置。
1 @Override 2 public void addFormatters(FormatterRegistry registry) { 3 for (Converter<?, ?> converter : getBeansOfType(Converter.class)) { 4 registry.addConverter(converter); 5 } 6 for (GenericConverter converter : getBeansOfType(GenericConverter.class)) { 7 registry.addConverter(converter); 8 } 9 for (Formatter<?> formatter : getBeansOfType(Formatter.class)) { 10 registry.addFormatter(formatter); 11 } 12 } 13 14 private <T> Collection<T> getBeansOfType(Class<T> type) { 15 return this.beanFactory.getBeansOfType(type).values(); 16 }
在該方法中能夠看到,從容器中獲取了全部的轉換器進行註冊。因此咱們若是要本身定義轉換器,只須要將自定義的轉換器註冊到容器便可。
消息轉換器(HttpMessageConvert)是 SpringMVC 用來轉換 HTTP 請求和響應的,例如咱們要將一個 JavaBean 實例以 Json 方式輸出,此時就會用到消息轉換器。消息轉換器的註冊定義在 SpringMVC 的自動配置類的一個內部類中,這裏貼出部分源碼:
1 @Configuration 2 @Import(EnableWebMvcConfiguration.class) 3 @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) 4 public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter { 5 6 private static final Log logger = LogFactory 7 .getLog(WebMvcConfigurerAdapter.class); 8 9 private final ResourceProperties resourceProperties; 10 11 private final WebMvcProperties mvcProperties; 12 13 private final ListableBeanFactory beanFactory; 14 15 private final HttpMessageConverters messageConverters; 16 17 final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; 18 19 public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, 20 WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, 21 @Lazy HttpMessageConverters messageConverters, 22 ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) { 23 this.resourceProperties = resourceProperties; 24 this.mvcProperties = mvcProperties; 25 this.beanFactory = beanFactory; 26 this.messageConverters = messageConverters; 27 this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider 28 .getIfAvailable(); 29 } 30 31 @Override 32 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 33 converters.addAll(this.messageConverters.getConverters()); 34 }
能夠看到 messageConverters 是經過構造方法以懶加載的形式注入,即 messageConverters 一樣是註冊在容器中。繼續查看 messageConverters 對應類:
1 public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> { 2 3 private static final List<Class<?>> NON_REPLACING_CONVERTERS; 4 5 static { 6 List<Class<?>> nonReplacingConverters = new ArrayList<Class<?>>(); 7 addClassIfExists(nonReplacingConverters, "org.springframework.hateoas.mvc." 8 + "TypeConstrainedMappingJackson2HttpMessageConverter"); 9 NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters); 10 } 11 12 private final List<HttpMessageConverter<?>> converters; 13 14 /** 15 * Create a new {@link HttpMessageConverters} instance with the specified additional 16 * converters. 17 * @param additionalConverters additional converters to be added. New converters will 18 * be added to the front of the list, overrides will replace existing items without 19 * changing the order. The {@link #getConverters()} methods can be used for further 20 * converter manipulation. 21 */ 22 public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) { 23 this(Arrays.asList(additionalConverters)); 24 } 25 26 /** 27 * Create a new {@link HttpMessageConverters} instance with the specified additional 28 * converters. 29 * @param additionalConverters additional converters to be added. Items are added just 30 * before any default converter of the same type (or at the front of the list if no 31 * default converter is found) The {@link #postProcessConverters(List)} method can be 32 * used for further converter manipulation. 33 */ 34 public HttpMessageConverters( 35 Collection<HttpMessageConverter<?>> additionalConverters) { 36 this(true, additionalConverters); 37 } 38 39 /** 40 * Create a new {@link HttpMessageConverters} instance with the specified converters. 41 * @param addDefaultConverters if default converters should be added 42 * @param converters converters to be added. Items are added just before any default 43 * converter of the same type (or at the front of the list if no default converter is 44 * found) The {@link #postProcessConverters(List)} method can be used for further 45 * converter manipulation. 46 */ 47 public HttpMessageConverters(boolean addDefaultConverters, 48 Collection<HttpMessageConverter<?>> converters) { 49 List<HttpMessageConverter<?>> combined = getCombinedConverters(converters, 50 addDefaultConverters ? getDefaultConverters() 51 : Collections.<HttpMessageConverter<?>>emptyList()); 52 combined = postProcessConverters(combined); 53 this.converters = Collections.unmodifiableList(combined); 54 }
能夠看到該類實現了迭代器接口,且構造函數使用了可變參數,即會將容器中全部的 HttpMessageConverter 注入。因此咱們若是要使用本身定義的消息轉換器,也只須要將消息轉換器註冊到容器便可。
消息代碼處理器(MessageCodesResolver)是用於定義 SpringMVC 的消息代碼的生成規則。
1 @Override 2 public MessageCodesResolver getMessageCodesResolver() { 3 if (this.mvcProperties.getMessageCodesResolverFormat() != null) { 4 DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver(); 5 resolver.setMessageCodeFormatter( 6 this.mvcProperties.getMessageCodesResolverFormat()); 7 return resolver; 8 } 9 return null; 10 }
數據綁定初始化器(ConfigurableWebBindingInitializer)是初始化 Web 數據綁定器(WebDataBinder),而 Web 數據綁定器是用來綁定 web 數據的,好比將請求參數綁定到 JavaBean 中就會用到 Web 數據綁定器。
1 @Override 2 protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() { 3 try { 4 return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class); 5 } 6 catch (NoSuchBeanDefinitionException ex) { 7 return super.getConfigurableWebBindingInitializer(); 8 } 9 }
SpringBoot 在自動配置不少組件時,會先判斷容器中有沒有用戶已註冊的對應組件,若是沒有,才自動配置它提供的默認組件。若是有些組件能夠有多個(例如視圖解析器),那麼 SpringBoot 會將用戶本身的配置和默認的配置組合起來。
下面是官方文檔關於擴展配置的一段描述:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
.若是你想在保留 Spring Boot MVC 一些默認配置的基礎上添加一些額外的 MVC 配置,例如:攔截器、格式化器、視圖控制器等,你可使用 @Configuration 註解自定義一個 WebMvcConfigurer 類但不能使用 @EnableWebMvc。
若是你想徹底控制 Spring MVC,你能夠在你的配置類上同時使用 @Configuration 和 @EnableWebMvc。
WebMvcConfigurerAdapter 是 WebMvcConfigurer 的適配器類,按照上面描述咱們能夠定義 SpringMVC 擴展配置類以下:
package com.springboot.webdev1.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * 要擴展什麼功能直接重寫對應方法便可 * 既保留了全部自動配置,也使用了咱們擴展的配置 */ @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { /** * 添加視圖映射 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { // 請求路徑 /test 將會轉發到名爲 test 的視圖 registry.addViewController("/test").setViewName("test"); } }
再次查看 SpringMVC 的自動配置類會發現該類有一個內部類也是繼承了 WebMvcConfigurerAdapter ,即 SpringBoot 也是經過該方式實現 SpringMVC 的自動配置:
@Configuration @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
在該類上有一個註解 @Import(EnableWebMvcConfiguration.class) ,查看 EnableWebMvcConfiguration 類:
@Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
接着查看 DelegatingWebMvcConfiguration 類:
1 package org.springframework.web.servlet.config.annotation; 2 3 import java.util.List; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.context.annotation.Configuration; 7 import org.springframework.format.FormatterRegistry; 8 import org.springframework.http.converter.HttpMessageConverter; 9 import org.springframework.validation.MessageCodesResolver; 10 import org.springframework.validation.Validator; 11 import org.springframework.web.method.support.HandlerMethodArgumentResolver; 12 import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 13 import org.springframework.web.servlet.HandlerExceptionResolver; 14 15 @Configuration 16 public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { 17 18 private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); 19 20 21 @Autowired(required = false) 22 public void setConfigurers(List<WebMvcConfigurer> configurers) { 23 if (configurers == null || configurers.isEmpty()) { 24 return; 25 } 26 this.configurers.addWebMvcConfigurers(configurers); 27 } 28 29 30 @Override 31 protected void addInterceptors(InterceptorRegistry registry) { 32 this.configurers.addInterceptors(registry); 33 } 34 35 @Override 36 protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) { 37 this.configurers.configureContentNegotiation(configurer); 38 } 39 40 @Override 41 public void configureAsyncSupport(AsyncSupportConfigurer configurer) { 42 this.configurers.configureAsyncSupport(configurer); 43 } 44 45 @Override 46 public void configurePathMatch(PathMatchConfigurer configurer) { 47 this.configurers.configurePathMatch(configurer); 48 } 49 50 @Override 51 protected void addViewControllers(ViewControllerRegistry registry) { 52 this.configurers.addViewControllers(registry); 53 } 54 55 @Override 56 protected void configureViewResolvers(ViewResolverRegistry registry) { 57 this.configurers.configureViewResolvers(registry); 58 } 59 60 @Override 61 protected void addResourceHandlers(ResourceHandlerRegistry registry) { 62 this.configurers.addResourceHandlers(registry); 63 } 64 65 @Override 66 protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { 67 this.configurers.configureDefaultServletHandling(configurer); 68 } 69 70 @Override 71 protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { 72 this.configurers.addArgumentResolvers(argumentResolvers); 73 } 74 75 @Override 76 protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { 77 this.configurers.addReturnValueHandlers(returnValueHandlers); 78 } 79 80 @Override 81 protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 82 this.configurers.configureMessageConverters(converters); 83 } 84 85 @Override 86 protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { 87 this.configurers.extendMessageConverters(converters); 88 } 89 90 @Override 91 protected void addFormatters(FormatterRegistry registry) { 92 this.configurers.addFormatters(registry); 93 } 94 95 @Override 96 protected Validator getValidator() { 97 return this.configurers.getValidator(); 98 } 99 100 @Override 101 protected MessageCodesResolver getMessageCodesResolver() { 102 return this.configurers.getMessageCodesResolver(); 103 } 104 105 @Override 106 protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { 107 this.configurers.configureHandlerExceptionResolvers(exceptionResolvers); 108 } 109 110 @Override 111 protected void addCorsMappings(CorsRegistry registry) { 112 this.configurers.addCorsMappings(registry); 113 } 114 115 }
能夠看到經過第 22 行的 setConfigurers(List<WebMvcConfigurer> configurers) 方法注入了全部的 WebMvcConfigurer 實例,即包含了咱們本身編寫的 WebMvcConfigurer bean。這就是咱們自定義 WebMvcConfigurer 能生效的緣由。
全面接管 SpringMVC 配置在上面描述中也有說明,咱們只須要在配置類的基礎上再使用 @EnableWebMvc 註解便可:
package com.springboot.webdev1.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * 全面接管 SpringMVC 配置,Spring 的自動配置已失效 */ @EnableWebMvc @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { /** * 添加視圖映射 * @param registry */ @Override public void addViewControllers(ViewControllerRegistry registry) { // 請求路徑 /test 將會轉發到名爲 test 的視圖 registry.addViewController("/test").setViewName("test"); } }
爲何加上 @EnableWebMvc 註解 SpringBoot 對於 SpringMVC 的自動配置就失效了呢?查看 SpringMVC 的自動配置類:
1 @Configuration 2 @ConditionalOnWebApplication 3 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, 4 WebMvcConfigurerAdapter.class }) 5 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 6 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) 7 @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, 8 ValidationAutoConfiguration.class }) 9 public class WebMvcAutoConfiguration {
能夠看到配置類上有一個 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 註解,該註解的意思就是當容器中不存在 WebMvcConfigurationSupport 這個類型的 bean 時自動配置類才生效。再查看 @EnableWebMvc 註解:
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target(ElementType.TYPE) 3 @Documented 4 @Import(DelegatingWebMvcConfiguration.class) 5 public @interface EnableWebMvc { 6 }
能夠看到它使用 @Import(DelegatingWebMvcConfiguration.class) 給容器中導入了 DelegatingWebMvcConfiguration 類型的 bean,而 DelegatingWebMvcConfiguration 又繼承自 WebMvcConfigurationSupport ,即便用了 @EnableWebMvc 註解後容器中會注入 WebMvcConfigurationSupport 類型的 bean,此時就與 SpringMVC 自動配置類上的 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 註解條件相斥,因此自動配置類失效。