本篇文章内容详细可参考官方文档第 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) 注解条件相斥,因此自动配置类失效。