java框架之SpringBoot(5)-SpringMVC的自動配置

本篇文章內容詳細可參考官方文檔第 29 節html

SpringMVC介紹

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的自動配置

SpringMVC 的自動配置類爲 org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration  。spring-mvc

視圖解析器

SpringBoot 爲 SpringMVC 自動配置了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 即視圖解析器。springboot

  • 視圖解析器能夠根據方法的返回值獲得對應的視圖對象,視圖對象來決定如何渲染到頁面。

BeanNameViewResolver

查看 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 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#beanNameViewResolver

查看 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 }
org.springframework.web.servlet.view.BeanNameViewResolver#resolveViewName

能夠看到,該方法就是在容器中查看是否包含對應名稱的 View 實例,若是包含,則直接返回。app

ContentNegotiatingViewResolver

查看 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;
}
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#viewResolver

查看 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 }
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#resolveViewName

看到第 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 }
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#getCandidateViews

能夠看到該方法實際上是在遍歷 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 }
org.springframework.web.servlet.view.ContentNegotiatingViewResolver#initServletContext

這個方法其實是經過 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 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#dateFormatter

能夠看到,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 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addFormatters

在該方法中能夠看到,從容器中獲取了全部的轉換器進行註冊。因此咱們若是要本身定義轉換器,只須要將自定義的轉換器註冊到容器便可。

消息轉換器

消息轉換器(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     }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

能夠看到 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     }
org.springframework.boot.autoconfigure.web.HttpMessageConverters

能夠看到該類實現了迭代器接口,且構造函數使用了可變參數,即會將容器中全部的 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 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#getMessageCodesResolver

數據綁定初始化器

數據綁定初始化器(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 }
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration#getConfigurableWebBindingInitializer

SpringBoot 在自動配置不少組件時,會先判斷容器中有沒有用戶已註冊的對應組件,若是沒有,才自動配置它提供的默認組件。若是有些組件能夠有多個(例如視圖解析器),那麼 SpringBoot 會將用戶本身的配置和默認的配置組合起來。

擴展SpringMVC配置

下面是官方文檔關於擴展配置的一段描述:

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 type WebMvcConfigurer 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。

定義SpringMVC配置類

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");
    }
}
com.springboot.webdev1.config.MyMvcConfig

再次查看 SpringMVC 的自動配置類會發現該類有一個內部類也是繼承了 WebMvcConfigurerAdapter ,即 SpringBoot 也是經過該方式實現 SpringMVC 的自動配置:

@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

在該類上有一個註解 @Import(EnableWebMvcConfiguration.class) ,查看 EnableWebMvcConfiguration 類:

@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {   
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.EnableWebMvcConfiguration

接着查看 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 }
org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration

能夠看到經過第 22 行的 setConfigurers(List<WebMvcConfigurer> configurers) 方法注入了全部的 WebMvcConfigurer 實例,即包含了咱們本身編寫的 WebMvcConfigurer  bean。這就是咱們自定義 WebMvcConfigurer 能生效的緣由。

全面接管SpringMVC配置

全面接管 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");
    }
}
com.springboot.webdev1.config.MyMvcConfig

爲何加上 @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 {
org.springframework.boot.autoconfigure.web.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 }
org.springframework.web.servlet.config.annotation.EnableWebMvc

能夠看到它使用 @Import(DelegatingWebMvcConfiguration.class) 給容器中導入了 DelegatingWebMvcConfiguration 類型的 bean,而 DelegatingWebMvcConfiguration 又繼承自 WebMvcConfigurationSupport ,即便用了 @EnableWebMvc 註解後容器中會注入 WebMvcConfigurationSupport 類型的 bean,此時就與 SpringMVC 自動配置類上的 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 註解條件相斥,因此自動配置類失效。

相關文章
相關標籤/搜索