天天看點

Spring5源碼16-@EnableWebMvc注解原理

作者:在天上飛的的程式員

1. 需求及用途

我們以前自定義的九大元件給容器中一放,DispatcherServlet初始化看容器中有我們的,就不用自己的預設元件。導緻我們很多預設功能失去了。

我們的期望:

  • 自己的元件能生效
  • SpringMVC預設的還能生效 可以采用 @EnableWebMvc + WebMvcConfigure。@EnableWebMVC會給容器中導入九大元件,還都留了入口【我們用WebMvcConfigurer就可以定制】,而不是用配置檔案預設。

參考spring官網,我們發現@EnableWebMvc注解和<mvc:annotation-driven>具有相同的功能,都是用來啟用springmvc的預設配置。當掃描到這個注解之後,就會向容器中注入一些預設元件。

那麼這個注解是如何實作springmvc預設配置的呢?往下看

2. @EnableWebMvc

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}           
我們發現,實際上就是通過@Import注解向容器中注冊了一個元件DelegatingWebMvcConfiguration,是以現在核心是DelegatingWebMvcConfiguration類幹了什麼?

3. DelegatingWebMvcConfiguration

先來看一下類圖:

Spring5源碼16-@EnableWebMvc注解原理

兩個Aware接口向該元件中自動注入一個ApplicationContext對象和ServletContext對象。現在我們要帶着兩個問題去看一下該類的源碼?

  • 為什麼這個元件能夠使使用者自定義的配置類生效呢?
  • 這個元件定義了哪些預設配置?

3.1 DelegatingWebMvcConfiguration源碼

DelegatingWebMvcConfiguration類非常簡單,隻有一個字段configurers,持有所有的配置類對象。另外還需要注意一個方法setConfigurers(List<WebMvcConfigurer> configurers)方法, 該方法标注了@Autowired(required = false)注解,會被自動執行。

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

    //組合模式,所有配置類組合
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

    /**
     * 此處添加了@Autowired(required = false)注解
     * 那麼該方法在屬性填充階段就會被執行,其中方法參數來自于容器
     * 會自動從容器中找到類型比對的bean,然後反射執行方法
     */
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            //将容器中WebMvcConfigurer對象全部放入WebMvcConfigurerComposite中
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }


    /********************************************************************************/

    /**
     * 下面的這些方法都差不多
     * 向WebMvcConfigurerComposite的每一個配置類中添加配置具體的過程見3.1.1
     */
    @Override
    protected void configurePathMatch(PathMatchConfigurer configurer) {
        this.configurers.configurePathMatch(configurer);
    }

    ...
    
    /********************************************************************************/

    //這個方法會擷取配置類定義的唯一的Validator
    @Override
    @Nullable
    protected Validator getValidator() {
        return this.configurers.getValidator();
    }

    //這個方法會擷取配置類定義的唯一的MessageCodesResolver
    @Override
    @Nullable
    protected MessageCodesResolver getMessageCodesResolver() {
        return this.configurers.getMessageCodesResolver();
    }
}           

可以看到,在DelegatingWebMvcConfiguration元件屬性填充階段,會自動的查找容器中所有類型為WebMvcConfigurer的bean對象,然後以它們為參數,反射調用setConfigurers(List<WebMvcConfigurer> configurers)方法,将所有的配置類對象放入WebMvcConfigurerComposite中。

除此之外,它還重寫了父類的一些方法(模闆方法模式),用來向配置類中注冊配置和擷取配置類中一些配置對象,比如Validator或MessageCodesResolver。

3.2 WebMvcConfigurerComposite

下面是該類的源碼,方法都很簡單,主要注意以下四點:

  • 該類持有所有的配置類對象
  • 添加一項配置的時候會将配置注冊給所有的配置類對象
  • getValidator()方法隻能被一個配置類重寫,隻允許一個配置類傳回一個Validator對象
  • getMessageCodesResolver()方法隻能被一個配置類重寫,隻允許一個配置類傳回一個MessageCodesResolver對象
class WebMvcConfigurerComposite implements WebMvcConfigurer {

    //持有的配置類對象
    private final List<WebMvcConfigurer> delegates = new ArrayList<>();


    public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.delegates.addAll(configurers);
        }
    }


    //添加一項配置的時候會将配置注冊給所有的配置類對象
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.configurePathMatch(configurer);
        }
    }

    ...

    /**
     * 這個方法需要注意一下
     * Validator必須是唯一的,也就說,隻允許一個配置重寫該方法傳回一個Validator
     * 有多個就會抛出異常
     */
    @Override
    public Validator getValidator() {
        Validator selected = null;
        for (WebMvcConfigurer configurer : this.delegates) {
            Validator validator = configurer.getValidator();
            if (validator != null) {
                if (selected != null) {
                    throw new IllegalStateException("No unique Validator found: {" +
                                                    selected + ", " + validator + "}");
                }
                selected = validator;
            }
        }
        return selected;
    }


    /**
     * 這個方法也需要注意一下
     * MessageCodesResolver必須是唯一的,也就說,
     * 隻允許一個配置重寫該方法傳回一個MessageCodesResolver,有多個就會抛出異常
     */
    @Override
    @Nullable
    public MessageCodesResolver getMessageCodesResolver() {
        MessageCodesResolver selected = null;
        for (WebMvcConfigurer configurer : this.delegates) {
            MessageCodesResolver messageCodesResolver = configurer.getMessageCodesResolver();
            if (messageCodesResolver != null) {
                if (selected != null) {
                    throw new IllegalStateException("No unique MessageCodesResolver found: {" +
                                                    selected + ", " + messageCodesResolver + "}");
                }
                selected = messageCodesResolver;
            }
        }
        return selected;
    }

}           

3.3 WebMvcConfigurationSupport

這是最重要的一個類,該類裡面有很多@Bean方法,用來向容器中注冊元件。

3.3.1 靜态私有字段

private static final boolean romePresent;

private static final boolean jaxb2Present;

private static final boolean jackson2Present;

private static final boolean jackson2XmlPresent;

private static final boolean jackson2SmilePresent;

private static final boolean jackson2CborPresent;

private static final boolean gsonPresent;

private static final boolean jsonbPresent;

static {
    ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
    romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
    jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
    jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
    jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
    jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
    jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
    gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
    jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}           

這些靜态字段是一些标志量,通過ClassUtils.isPresent()方法判斷是否導入了對應的包,如果導入了,就将對應的靜态字段置為true,後面就可以根據這些标志量反射執行個體化對象了。

這也解釋了為什麼隻要我們導入了jackson包,就可以自動向HandlerAdapter中注冊MappingJackson2HttpMessageConverter和MappingJackson2XmlHttpMessageConverter對象。

3.3.2 普通字段

//web應用上下文
@Nullable
private ApplicationContext applicationContext;

//servlet上下文
@Nullable
private ServletContext servletContext;

//攔截器
@Nullable
private List<Object> interceptors;

//路徑比對配置器
@Nullable
private PathMatchConfigurer pathMatchConfigurer;

//内容協商管理器
@Nullable
private ContentNegotiationManager contentNegotiationManager;

//參數解析器
@Nullable
private List<HandlerMethodArgumentResolver> argumentResolvers;

//傳回值處理器
@Nullable
private List<HandlerMethodReturnValueHandler> returnValueHandlers;

//http消息轉換器
@Nullable
private List<HttpMessageConverter<?>> messageConverters;

//跨域配置
@Nullable
private Map<String, CorsConfiguration> corsConfigurations;           

後面的小章節都是該類定義的@Bean方法,我們看看它到底向容器中放入了哪些元件?

3.3.3 注冊RequestMappingHandlerMapping

@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

   //建立一個RequestMappingHandlerMapping對象,就是簡單的new一個
   RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
   //設定順序,第一位
   mapping.setOrder(0);
   /**
    * getInterceptors()方法可以擷取到使用者注冊和系統預設的所有攔截器對象
    * 然後将這些攔截器全部放入處理器映射器中
    */
   mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
   //設定内容協商管理器
   mapping.setContentNegotiationManager(contentNegotiationManager);
   //擷取使用者注冊的所有跨域配置
   mapping.setCorsConfigurations(getCorsConfigurations());

   //擷取路徑比對配置器
   PathMatchConfigurer pathConfig = getPathMatchConfigurer();
   if (pathConfig.getPatternParser() != null) {
      mapping.setPatternParser(pathConfig.getPatternParser());
   }
   else {
      //擷取路徑比對配置器中設定的UrlPathHelper,使用這個UrlPathHelper覆寫預設的UrlPathHelper
      mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
      //擷取在路徑比對配置器中配置的路徑比對器,覆寫預設的路徑比對器
      mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());

      //已經被廢棄掉了不推薦使用,直接跳過
      Boolean useSuffixPatternMatch = pathConfig.isUseSuffixPatternMatch();
      if (useSuffixPatternMatch != null) {
         mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
      }
      //已經被廢棄掉了不推薦使用,直接跳過
      Boolean useRegisteredSuffixPatternMatch = pathConfig.isUseRegisteredSuffixPatternMatch();
      if (useRegisteredSuffixPatternMatch != null) {
         mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
      }
   }
   //是否使用尾斜杠比對
   Boolean useTrailingSlashMatch = pathConfig.isUseTrailingSlashMatch();
   if (useTrailingSlashMatch != null) {
      mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
   }
   //擷取配置所有路徑字首
   if (pathConfig.getPathPrefixes() != null) {
      mapping.setPathPrefixes(pathConfig.getPathPrefixes());
   }

   return mapping;
}           

我們可以發現,這個方法為RequestMappingHandlerMapping做了一大堆配置,如下所示

  • 擷取使用者和系統配置的所有攔截器并注冊進去
  • 擷取容器中内容協商管理器并注冊進去
  • 擷取使用者注冊的所有跨域配置并注冊進去
  • 擷取路徑比對配置器,然後将路徑比對配置器的配置覆寫進去,主要覆寫一下4中配置
  • 尾斜杠比對
  • UrlPathHelper
  • PathMatcher
  • pathPrefixes

3.3.3.1 建立一個RequestMappingHandlerMapping對象

/**
 * Protected method for plugging in a custom subclass of
 * {@link RequestMappingHandlerMapping}.
 * @since 4.0
 */
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
    return new RequestMappingHandlerMapping();
}           

就是簡單的new一個RequestMappingHandlerMapping對象

3.3.3.2 擷取使用者注冊和系統預設的所有攔截器對象

protected final Object[] getInterceptors(
    FormattingConversionService mvcConversionService,
    ResourceUrlProvider mvcResourceUrlProvider) {
    if (this.interceptors == null) {
        //攔截器注冊中心
        InterceptorRegistry registry = new InterceptorRegistry();
        /**
         * 該方法被子類重寫,會以這個攔截器注冊中心為參數調用所有配置類對象中
         * addInterceptors(registry)方法,使用者通過這個注冊中心注冊攔截器對象。
         * 很明顯,此處調用addInterceptors(registry)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * addInterceptors(registry)方法,将配置類中配置的攔截器注冊到攔截器注冊中心
         * 這個注冊中心會将所有類型的攔截器統一适配為InterceptorRegistration類型,友善管理
         */
        addInterceptors(registry);
        //這個攔截器将FormattingConversionService儲存到請求域中
        registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService));
        //這個攔截器将ResourceUrlProvider儲存到請求域中
        registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider));
        //擷取注冊中心中所有的攔截器,會先排序,再傳回
        this.interceptors = registry.getInterceptors();
    }
    return this.interceptors.toArray();
}           

我們重點看一下這個攔截器注冊中心是如何注冊攔截器的

// org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration#addInterceptors
@Override
protected void addInterceptors(InterceptorRegistry registry) {
   this.configurers.addInterceptors(registry);
}

// org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite#addInterceptors
@Override
public void addInterceptors(InterceptorRegistry registry) {
   for (WebMvcConfigurer delegate : this.delegates) {
      delegate.addInterceptors(registry);
   }
}           

這個方法是攔截器相關的核心方法了,它會執行每一個WebMvcConfigurer配置類的addInterceptors(registry)方法, 将使用者自定義的攔截器注冊到攔截器注冊中心中,最後再放入兩個系統預設的攔截器,然後統一排序,并傳回

3.3.3.3 擷取使用者注冊的所有跨域配置

protected final Map<String, CorsConfiguration> getCorsConfigurations() {
    if (this.corsConfigurations == null) {
        //跨域配置注冊中心
        CorsRegistry registry = new CorsRegistry();
        /**
         * 和上面攔截器一樣,會以這個跨域配置注冊中心為參數調用所有配置類對象中
         * addCorsMappings(registry)方法,使用者通過這個注冊中心注冊跨域配置。
         * 很明顯,此處調用addCorsMappings(registry)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * addCorsMappings(registry)方法,将配置類中配置的跨域配置注冊到注冊中心
         */
        addCorsMappings(registry);
        //得到注冊中心的所有的跨域配置
        this.corsConfigurations = registry.getCorsConfigurations();
    }
    return this.corsConfigurations;
}           

和上面攔截器一樣,它會執行每一個WebMvcConfigurer配置類的addCorsMappings(registry)方法, 将使用者自定義的跨域配置注冊到跨域配置注冊中心,最後傳回所有的跨域配置

3.3.3.4 擷取路徑比對配置器

protected PathMatchConfigurer getPathMatchConfigurer() {
    if (this.pathMatchConfigurer == null) {
        //建立一個路徑比對配置器
        this.pathMatchConfigurer = new PathMatchConfigurer();
        /**
         * 又是同樣的方式,會以這個路徑比對配置器為參數調用所有配置類對象中
         * configurePathMatch(pathMatchConfigurer)方法,使用者通過這個配置器注冊路徑比對器。
         * 很明顯,此處調用configurePathMatch(pathMatchConfigurer)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * configurePathMatch(pathMatchConfigurer)方法,将配置類中配置的路徑比對器
         * 注冊到配置器中
         */
        configurePathMatch(this.pathMatchConfigurer);
    }
    return this.pathMatchConfigurer;
}           

同樣的方式,它會執行每一個WebMvcConfigurer配置類的configurePathMatch(pathMatchConfigurer)方法, 将使用者自定義的路徑比對器注冊到路徑比對配置器中,最後傳回這個路勁比對配置器

3.3.4 注冊PathMatcher

@Bean
public PathPatternParser mvcPatternParser() {
   /**
    * getPathMatchConfigurer()方法,擷取路徑比對配置器,見3.3.3.4
    * 然後得到這個配置器中的路徑比對器,把它放入容器中
    * 如果使用者未配置,則使用預設的AntPathMatcher
    */
   return getPathMatchConfigurer().getPatternParserOrDefault();
}           

先嘗試從PathMatchConfigurer路徑比對配置器中取出使用者配置的PathMatcher路徑比對器,若存在,則直接将使用者配置的放入容器中,否則傳回defaultPatternParser。

3.3.5 注冊UrlPathHelper

@Bean
public UrlPathHelper mvcUrlPathHelper() {
   /**
    * getPathMatchConfigurer()方法,擷取路徑比對配置器,見3.3.3.4
    * 然後得到這個配置器中的UrlPathHelper,把它放入容器中
    * 如果使用者未配置,則使用預設的UrlPathHelper
    */
   return getPathMatchConfigurer().getUrlPathHelperOrDefault();
}           

先嘗試從PathMatchConfigurer路徑比對配置器中取出使用者配置的UrlPathHelper,若存在,則直接将使用者配置的放入容器中,否則建立一個新的UrlPathHelper對象放入容器中

3.3.6 注冊ContentNegotiationManager

@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
    if (this.contentNegotiationManager == null) {
        //建立一個内容協商配置器
        ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
        //将伺服器預設能生産的媒體類型儲存到内容協商配置器
        configurer.mediaTypes(getDefaultMediaTypes());
        /**
         * 和上面攔截器一樣,會以這個内容協商配置器為參數調用所有配置類對象中
         * configureContentNegotiation(configurer)方法,使用者通過這個配置器注冊和
         * 修改内容協商管理器。很明顯,此處調用configureContentNegotiation(configurer)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * configureContentNegotiation(configurer)方法,
         * 将配置類中配置的内容協商管理器注冊到配置器中
         */
        configureContentNegotiation(configurer);
        //根據這個配置器的配置建立一個内容協商管理器
        this.contentNegotiationManager = configurer.buildContentNegotiationManager();
    }
    return this.contentNegotiationManager;
}           

下面這個方法會擷取到伺服器預設能生産的媒體類型,實際上就判斷是否導入了對應包

protected Map<String, MediaType> getDefaultMediaTypes() {
    Map<String, MediaType> map = new HashMap<>(4);
    //根據3.3.1的這些标志量,來得到伺服器能夠生産的媒體類型
    if (romePresent) {
        map.put("atom", MediaType.APPLICATION_ATOM_XML);
        map.put("rss", MediaType.APPLICATION_RSS_XML);
    }
    //導了jackson包就能生産xml,json
    if (jaxb2Present || jackson2XmlPresent) {
        map.put("xml", MediaType.APPLICATION_XML);
    }
    if (jackson2Present || gsonPresent || jsonbPresent) {
        map.put("json", MediaType.APPLICATION_JSON);
    }
    if (jackson2SmilePresent) {
        map.put("smile", MediaType.valueOf("application/x-jackson-smile"));
    }
    if (jackson2CborPresent) {
        map.put("cbor", MediaType.APPLICATION_CBOR);
    }
    return map;
}           

并不是直接new一個ContentNegotiationManager對象,而是先建立一個ContentNegotiationConfigurer對象,然後将使用者的自定義配置儲存在ContentNegotiationConfigurer中,由ContentNegotiationConfigurer來生産ContentNegotiationManager對象,就是工廠模式,隐藏對象建立的細節。

3.3.7 注冊viewControllerHandlerMapping

@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

   //視圖控制注冊中心
   ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
   /**
    * addViewControllers(registry)這個方法我們應該不陌生,我們經常在配置類中重寫該方法,
    * 注冊路徑->視圖的映射關系。
    * 此處調用addViewControllers(registry)方法,
    * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
    * addViewControllers(registry)方法,注冊路徑->視圖的映射關系
    */
   addViewControllers(registry);
   //真實類型為SimpleUrlHandlerMapping
   AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
   if (handlerMapping == null) {
      return null;
   }
   PathMatchConfigurer pathConfig = getPathMatchConfigurer();
   if (pathConfig.getPatternParser() != null) {
      handlerMapping.setPatternParser(pathConfig.getPatternParser());
   }
   else {
      handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
      handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
   }
   //将所有的攔截器設定進去
   handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
   //跨域配置
   handlerMapping.setCorsConfigurations(getCorsConfigurations());
   return handlerMapping;
}           

這個還是很重要的:我們經常在配置類中重寫addViewControllers(registry)方法,注冊路徑->視圖(ParameterizableViewController)的映射關系。最終由SimpleControllerHandlerAdapter調用ParameterizableViewController的handleRequest()方法完成視圖處理,見3.3.19

3.3.8 注冊BeanNameUrlHandlerMapping

@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

   BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
   mapping.setOrder(2);
   mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
   mapping.setCorsConfigurations(getCorsConfigurations());
   return mapping;
}           

3.3.9 注冊RouterFunctionMapping

@Bean
public RouterFunctionMapping routerFunctionMapping(
    @Qualifier("mvcConversionService") FormattingConversionService conversionService,
    @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

    RouterFunctionMapping mapping = new RouterFunctionMapping();
    mapping.setOrder(3);
    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    mapping.setCorsConfigurations(getCorsConfigurations());
    mapping.setMessageConverters(getMessageConverters());
    return mapping;
}           

功能路由,類似于webflux,直接忽略

3.3.10 注冊resourceHandlerMapping

@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
    @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
    @Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
    @Qualifier("mvcConversionService") FormattingConversionService conversionService,
    @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

    Assert.state(this.applicationContext != null, "No ApplicationContext set");
    Assert.state(this.servletContext != null, "No ServletContext set");

    //建立資源處理器注冊中心
    ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
                                                                   this.servletContext, contentNegotiationManager, urlPathHelper);
    /**
     * 會以這個資源處理器注冊中心為參數調用所有配置類對象中
     * addResourceHandlers(registry)方法,使用者通過這個資源處理器注冊中心設定靜态資源的
     * 路徑,靜态資源位置,靜态資源緩存時間等等。
     * 很明顯,此處調用addResourceHandlers(registry)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * addResourceHandlers(registry)方法,将配置類中靜态資源的配置儲存到資源處理器注冊中心中 
     */
    addResourceHandlers(registry);

    //通過資源處理器注冊中心建立處理靜态資源的SimpleUrlHandlerMapping
    AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
    if (handlerMapping == null) {
        return null;
    }
    //設定其它的一些配置,這些我們都見過,見3.3.3
    handlerMapping.setPathMatcher(pathMatcher);
    handlerMapping.setUrlPathHelper(urlPathHelper);
    handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    handlerMapping.setCorsConfigurations(getCorsConfigurations());
    return handlerMapping;
}           

這個HandlerMapping很重要,它用來處理靜态資源。通過重寫WebMvcConfigurer配置類的addResourceHandlers(registry)方法即可設定靜态資源的路徑,靜态資源位置,靜态資源緩存時間。然後沒有配置@RequestMapping注解映射的路徑就會交由這個HandlerMapping處理,隻有它也不能處理才會傳回404

3.3.11 注冊ResourceUrlProvider

@Bean
public ResourceUrlProvider mvcResourceUrlProvider() {
   ResourceUrlProvider urlProvider = new ResourceUrlProvider();
   //擷取使用者在路徑比對配置器配置的UrlPathHelper
   urlProvider.setUrlPathHelper(getPathMatchConfigurer().getUrlPathHelperOrDefault());
   //擷取使用者在路徑比對配置器配置的PathMatcher
   urlProvider.setPathMatcher(getPathMatchConfigurer().getPathMatcherOrDefault());
   return urlProvider;
}           

ResourceUrlProvider是一個監聽器,監聽ContextRefreshedEvent事件,即refresh()方法執行完成之前觸發。

3.3.12 注冊defaultServletHandlerMapping

@Bean
@Nullable
public HandlerMapping defaultServletHandlerMapping() {
    Assert.state(this.servletContext != null, "No ServletContext set");
    //預設Servlet處理配置器
    DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(this.servletContext);
    /**
     * 會以這個預設Servlet處理的配置器為參數調用所有配置類對象中
     * configureDefaultServletHandling(configurer)方法,使用者通過這個配置器啟用Tomcat容器
     * 預設的Servlet,将靜态資源請求轉發給這個預設的Servlet處理。
     * 很明顯,此處調用configureDefaultServletHandling(configurer)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * configureDefaultServletHandling(configurer)方法,啟用Tomcat容器預設的Servlet
     */
    configureDefaultServletHandling(configurer);
    //應用使用者配置建構一個SimpleUrlHandlerMapping
    return configurer.buildHandlerMapping();
}           

不知道大家知不知道springmmvc的這項配置<mvc:default-servlet-handler/>是什麼意思?

其實這個配置就是啟用Tomcat容器預設的Servlet處理靜态資源請求,和此處配置類的configureDefaultServletHandling(configurer)方法是對應的,使用者在這個方法中調用configurer.enable();就啟用了Tomcat容器預設的Servlet

3.3.13 注冊RequestMappingHandlerAdapter

@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
      @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
      @Qualifier("mvcConversionService") FormattingConversionService conversionService,
      @Qualifier("mvcValidator") Validator validator) {

   //建立一個RequestMappingHandlerAdapter對象
   RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();

   adapter.setContentNegotiationManager(contentNegotiationManager);
   /**
    * getMessageConverters()方法,擷取所有的消息轉換器,見3.3.13.2
    * 并将這些消息轉換器設定到處理器擴充卡中
    */
   adapter.setMessageConverters(getMessageConverters());
   /**
    * getConfigurableWebBindingInitializer()方法,擷取資料綁定器的初始化器,見3.3.13.3
    * 并将這個初始化器設定到處理器擴充卡中
    */
   adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
   /**
    * getArgumentResolvers()方法,擷取所有使用者自定義的參數解析器,見3.3.13.4
    * 并設定到處理器擴充卡中
    */
   adapter.setCustomArgumentResolvers(getArgumentResolvers());
   /**
    * getReturnValueHandlers()方法,擷取所有使用者自定義的傳回值處理器,見3.3.13.5
    * 并設定到處理器擴充卡中
    */
   adapter.setCustomReturnValueHandlers(getReturnValueHandlers());

   /**
    * 導入了jackson包,就放入兩個通知
    * 這兩個通知分别會在@RequestBody注解參數類型轉換前後執行
    * @ResponseBody注解方法傳回值寫入響應前後執行
    */
   if (jackson2Present) {
      adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
      adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
   }

   /*****************************異步支援的相關配置***********************************/
   // todo 異步支援配置器
   AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
   if (configurer.getTaskExecutor() != null) {
      //異步執行器,實際上就是線程池,異步完成處理器調用
      adapter.setTaskExecutor(configurer.getTaskExecutor());
   }
   if (configurer.getTimeout() != null) {
      //異步執行的逾時時間
      adapter.setAsyncRequestTimeout(configurer.getTimeout());
   }
   //異步支援的攔截器
   adapter.setCallableInterceptors(configurer.getCallableInterceptors());
   adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());

   return adapter;
}           

我們可以發現,這個方法為RequestMappingHandlerAdapter做了一大堆配置,如下所示

  • 擷取容器中内容協商管理器并注冊進去
  • 擷取所有的消息轉換器并注冊進去
  • 擷取資料綁定器的初始化器并注冊進去
  • 擷取所有使用者自定義的參數解析器并注冊進去
  • 擷取所有使用者自定義的傳回值處理器并注冊進去
  • 将使用者自定義的異步配置覆寫進去

3.3.13.1 建立一個RequestMappingHandlerAdapter對象

protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
    return new RequestMappingHandlerAdapter();
}           

就是簡單的new一個RequestMappingHandlerAdapter對象

3.3.13.2 擷取所有的消息轉換器

protected final List<HttpMessageConverter<?>> getMessageConverters() {
    if (this.messageConverters == null) {
        this.messageConverters = new ArrayList<>();
        /**
         * 此處調用configureMessageConverters(list)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * configureMessageConverters(list)方法,向這個集合中添加消息轉換器
         * 通過此方法添加消息轉換器,springmvc容器就不會注冊預設的消息轉換器了
         */
        configureMessageConverters(this.messageConverters);
        if (this.messageConverters.isEmpty()) {
            //添加預設的消息轉換器
            addDefaultHttpMessageConverters(this.messageConverters);
        }
        /**
         * 此處調用extendMessageConverters(list)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * extendMessageConverters(list)方法,向這個集合中添加消息轉換器
         * 這個方法是在系統預設的轉換器基礎額外添加使用者自定義的消息轉換器
         */
        extendMessageConverters(this.messageConverters);
    }
    return this.messageConverters;
}           

如果使用者未自定義消息轉換器,那麼springmvc就會添加一些預設的消息轉換器

protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
   messageConverters.add(new ByteArrayHttpMessageConverter());
   messageConverters.add(new StringHttpMessageConverter());
   messageConverters.add(new ResourceHttpMessageConverter());
   messageConverters.add(new ResourceRegionHttpMessageConverter());
   try {
      messageConverters.add(new SourceHttpMessageConverter<>());
   }
   catch (Throwable ex) {
      // Ignore when no TransformerFactory implementation is available...
   }
   messageConverters.add(new AllEncompassingFormHttpMessageConverter());

   if (romePresent) {
      messageConverters.add(new AtomFeedHttpMessageConverter());
      messageConverters.add(new RssChannelHttpMessageConverter());
   }

   if (jackson2XmlPresent) {
      Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
      if (this.applicationContext != null) {
         builder.applicationContext(this.applicationContext);
      }
      messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
   }
   else if (jaxb2Present) {
      messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
   }

   if (jackson2Present) {
      Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
      if (this.applicationContext != null) {
         builder.applicationContext(this.applicationContext);
      }
      messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
   }
   else if (gsonPresent) {
      messageConverters.add(new GsonHttpMessageConverter());
   }
   else if (jsonbPresent) {
      messageConverters.add(new JsonbHttpMessageConverter());
   }

   if (jackson2SmilePresent) {
      Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
      if (this.applicationContext != null) {
         builder.applicationContext(this.applicationContext);
      }
      messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
   }
   if (jackson2CborPresent) {
      Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
      if (this.applicationContext != null) {
         builder.applicationContext(this.applicationContext);
      }
      messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
   }
}           

可以看到,如果系統導入了對應的包,就會建立對應類型的消息轉換器

3.3.13.3 擷取資料綁定器的初始化器

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(
    FormattingConversionService mvcConversionService, Validator mvcValidator) {

    //直接new一個資料綁定器的初始化器
    ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
    //統一轉換服務
    initializer.setConversionService(mvcConversionService);
    //校驗器
    initializer.setValidator(mvcValidator);
    /**
     * 此處調用getMessageCodesResolver()方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * getMessageCodesResolver()方法,擷取使用者配置的消息解碼器
     * 隻允許一個配置類重寫getMessageCodesResolver()方法傳回一個消息解碼器
     */
    MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
    if (messageCodesResolver != null) {
        initializer.setMessageCodesResolver(messageCodesResolver);
    }
    return initializer;
}           

該方法應用使用者自定義配置,初始化一個ConfigurableWebBindingInitializer資料綁定器的初始化器

3.3.13.4 擷取所有使用者自定義的參數解析器

protected final List<HandlerMethodArgumentResolver> getArgumentResolvers() {
    if (this.argumentResolvers == null) {
        this.argumentResolvers = new ArrayList<>();
        /**
         * 此處調用addArgumentResolvers(list)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * addArgumentResolvers(list)方法,向這個集合中添加使用者自定義的參數解析器
         */
        addArgumentResolvers(this.argumentResolvers);
    }
    return this.argumentResolvers;
}           

該方法擷取到所有配置類addArgumentResolvers(list)方法配置的參數解析器

3.3.13.5 擷取所有使用者自定義的傳回值處理器

protected final List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
    if (this.returnValueHandlers == null) {
        this.returnValueHandlers = new ArrayList<>();
        /**
         * 此處調用addReturnValueHandlers(list)方法,
         * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
         * addReturnValueHandlers(list)方法,向這個集合中添加使用者自定義的傳回值處理器
         */
        addReturnValueHandlers(this.returnValueHandlers);
    }
    return this.returnValueHandlers;
}           

該方法擷取到所有配置類addReturnValueHandlers(list)方法配置的傳回值處理器

3.3.14 注冊HandlerFunctionAdapter

@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
   HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();

   AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();
   if (configurer.getTimeout() != null) {
      adapter.setAsyncRequestTimeout(configurer.getTimeout());
   }
   return adapter;
}           

與3.3.9節中注冊的RouterFunctionMapping對應

3.3.15 注冊FormattingConversionService

@Bean
public FormattingConversionService mvcConversionService() {
    //DefaultFormattingConversionService的構造方法會自動注冊一些預設的格式化器,類型轉換器
    FormattingConversionService conversionService = new DefaultFormattingConversionService();
    /**
     * 此處調用addFormatters(conversionService)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * addFormatters(conversionService)方法,向這個格式轉換服務中添加使用者自定義的配置
     * 比如注冊格式化器,類型轉換器等
     */
    addFormatters(conversionService);
    return conversionService;
}           

在FormattingConversionService注冊到容器之前,允許使用者向這個格式轉換服務中添加自定義的配置

3.3.16 注冊Validator

@Bean
public Validator mvcValidator() {
    /**
     * 此處調用getValidator()方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * getValidator()方法,擷取使用者配置的唯一的那一個Validator對象
     * 比如注冊格式化器,類型轉換器等
     */
    Validator validator = getValidator();
    /**
     * 使用者未手動配置Validator,但是
     * 導入了Validator對應的包,于是自動使用反射執行個體化一個Validator對象
     */
    if (validator == null) {
        if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
            Class<?> clazz;
            try {
                String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
                clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
            }
            catch (ClassNotFoundException | LinkageError ex) {
                throw new BeanInitializationException("Failed to resolve default validator class", ex);
            }
            validator = (Validator) BeanUtils.instantiateClass(clazz);
        }
        //不校驗的Validator
        else {
            validator = new NoOpValidator();
        }
    }
    return validator;
}           

向容器中注冊一個Validator,來源有三個地方

  • 使用者在WebMvcConfigurer配置類中配置的惟一的Validator
  • 導入了Validator包,然後系統自動使用反射執行個體化一個Validator對象
  • 沒有導入Validator包,那麼就建立一個NoOpValidator對象,這個對象裡面邏輯是空的,不進行校驗

3.3.17 注冊CompositeUriComponentsContributor

@Bean
public CompositeUriComponentsContributor mvcUriComponentsContributor(
    @Qualifier("mvcConversionService") FormattingConversionService conversionService,
    @Qualifier("requestMappingHandlerAdapter") RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
    return new CompositeUriComponentsContributor(
        requestMappingHandlerAdapter.getArgumentResolvers(), conversionService);
}           

這個暫時還不知道有什麼用途,跳過

3.3.18 注冊HttpRequestHandlerAdapter

@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
    return new HttpRequestHandlerAdapter();
}           

可用來處理器3.3.10和3.3.12節中注冊的SimpleUrlHandlerMapping傳回的處理器。它隻能處理實作了HttpRequestHandler接口的處理器

3.3.19 注冊SimpleControllerHandlerAdapter

@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
   return new SimpleControllerHandlerAdapter();
}           

可用來處理器3.3.7節中注冊的SimpleUrlHandlerMapping傳回的處理器。它隻能處理實作了Controller接口的處理器

3.3.20 注冊HandlerExceptionResolver

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    //處理器異常解析器
    List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
    /**
     * 此處調用configureHandlerExceptionResolvers(list)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * configureHandlerExceptionResolvers(list)方法,向這個集合中添加使用者自定義的
     * 處理器異常解析器。注意:通過此方法添加處理器異常解析器,springmvc容器就不會注冊預設的
     * 處理器異常解析器了
     */
    configureHandlerExceptionResolvers(exceptionResolvers);
    if (exceptionResolvers.isEmpty()) {
        //添加預設的處理器異常解析器
        addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
    }
    /**
     * 此處調用extendHandlerExceptionResolvers(list)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * extendHandlerExceptionResolvers(list)方法,向這個集合中添加處理器異常解析器
     * 這個方法是在系統預設的處理器異常解析器基礎額外添加使用者自定義的處理器異常解析器
     */
    extendHandlerExceptionResolvers(exceptionResolvers);
    //組合模式,多個處理器異常解析器完成解析功能
    HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
    composite.setOrder(0);
    composite.setExceptionResolvers(exceptionResolvers);
    return composite;
}           

如果使用者未自定義處理器異常解析器,那麼springmvc就會添加一些預設的處理器異常解析器

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
                                                         ContentNegotiationManager mvcContentNegotiationManager) {

    //建立一個使用@ExceptionHandler注解方法處理異常的異常解析器
    ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
    exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
    exceptionHandlerResolver.setMessageConverters(getMessageConverters());
    exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
    exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
    if (jackson2Present) {
        exceptionHandlerResolver.setResponseBodyAdvice(
            Collections.singletonList(new JsonViewResponseBodyAdvice()));
    }
    if (this.applicationContext != null) {
        exceptionHandlerResolver.setApplicationContext(this.applicationContext);
    }
    exceptionHandlerResolver.afterPropertiesSet();
    //1
    exceptionResolvers.add(exceptionHandlerResolver);

    ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
    responseStatusResolver.setMessageSource(this.applicationContext);
    //2
    exceptionResolvers.add(responseStatusResolver);

    //3
    exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}


/**
 * Protected method for plugging in a custom subclass of
 * {@link ExceptionHandlerExceptionResolver}.
 * @since 4.3
 */
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
    return new ExceptionHandlerExceptionResolver();
}           

預設注冊了三個異常解析器:

  • ExceptionHandlerExceptionResolver: @ExceptionHandler注解方法處理異常
  • ResponseStatusExceptionResolver: @ResponseStatus注解處理異常,響應指定狀态
  • DefaultHandlerExceptionResolver : 預設的異常解析器,根據異常類型響應指定的狀态碼

3.3.21 注冊ViewResolver

@Bean
public ViewResolver mvcViewResolver(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    //視圖解析器注冊中心
    ViewResolverRegistry registry =
        new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
    /**
     * 此處調用configureViewResolvers(registry)方法,
     * 就會帶着WebMvcConfigurerComposite中所有的配置類對象都調用一次
     * configureViewResolvers(registry)方法,向這個注冊中心中添加使用者自定義的
     * 視圖解析器
     */
    configureViewResolvers(registry);

    //使用者未在配置類中注冊視圖解析器
    if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
        //擷取容器中所有視圖解析器的beanName
        String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this.applicationContext, ViewResolver.class, true, false);
        //隻有一個,說明就是目前這個視圖解析器mvcViewResolver
        if (names.length == 1) {
            //添加一個預設的視圖解析器InternalResourceViewResolver
            registry.getViewResolvers().add(new InternalResourceViewResolver());
        }
    }

    //組合模式,多個視圖解析器先後解析一個視圖,使用第一個能解析的視圖解析器解析
    ViewResolverComposite composite = new ViewResolverComposite();
    composite.setOrder(registry.getOrder());
    composite.setViewResolvers(registry.getViewResolvers());
    if (this.applicationContext != null) {
        composite.setApplicationContext(this.applicationContext);
    }
    if (this.servletContext != null) {
        composite.setServletContext(this.servletContext);
    }
    return composite;
}           

有兩種配置視圖解析器的方式:

  • 直接放入容器中,可以自動被識别為視圖解析器
  • 通過WebMvcConfigurer配置類的configureViewResolvers(registry)方法注冊使用者自定義的視圖解析器
需要注意的是:兩種不能同時使用,否則第一種的就不會生效了

3.3.22 注冊HandlerMappingIntrospector

@Bean
@Lazy
public HandlerMappingIntrospector mvcHandlerMappingIntrospector() {
    return new HandlerMappingIntrospector();
}           

這個暫時還不知道有什麼用途,跳過

3.4 總結一下注冊的元件

使用@EnableWebMvc或<mvc:annotation-driven>一共會向容器中注冊20個元件,分别如下所示

requestMappingHandlerMapping->RequestMappingHandlerMapping
mvcPathMatcher->PathMatcher
mvcUrlPathHelper->UrlPathHelper
mvcContentNegotiationManager->ContentNegotiationManager
viewControllerHandlerMapping->SimpleUrlHandlerMapping
beanNameHandlerMapping->BeanNameUrlHandlerMapping
routerFunctionMapping->RouterFunctionMapping
resourceHandlerMapping->SimpleUrlHandlerMapping
mvcResourceUrlProvider->ResourceUrlProvider
defaultServletHandlerMapping->SimpleUrlHandlerMapping
requestMappingHandlerAdapter->RequestMappingHandlerAdapter
handlerFunctionAdapter->HandlerFunctionAdapter
mvcConversionService->FormattingConversionService
mvcValidator->Validator
mvcUriComponentsContributor->CompositeUriComponentsContributor
httpRequestHandlerAdapter->HttpRequestHandlerAdapter
simpleControllerHandlerAdapter->SimpleControllerHandlerAdapter
handlerExceptionResolver->HandlerExceptionResolverComposite
mvcViewResolver->ViewResolverComposite
mvcHandlerMappingIntrospector->HandlerMappingIntrospector           

4. NoOpValidator

該類是WebMvcConfigurationSupport的嵌套類,表示不使用校驗功能,可以看到,它的實作為空

private static final class NoOpValidator implements Validator {

   @Override
   public boolean supports(Class<?> clazz) {
      return false;
   }

   @Override
   public void validate(@Nullable Object target, Errors errors) {
   }
}           

5. @EnableMVC流程圖

Spring5源碼16-@EnableWebMvc注解原理