spring boot 源码解析18-WebMvcAutoConfiguration自动化配置揭秘

前言

上篇文章,我们分析了spring boot 中其他有关mvc的自动化配置类,只剩下WebMvcAutoConfiguration没有解析,这篇文章对其进行收尾

解析

  1. WebMvcAutoConfiguration 有如下注解:

    @Configuration
    @ConditionalOnWebApplication
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
        WebMvcConfigurerAdapter.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
    • @Configuration–>配置类
    • @ConditionalOnWebApplication–>web环境
    • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
      WebMvcConfigurerAdapter.class })–> 当前类路径下存在Servlet, DispatcherServlet,WebMvcConfigurerAdapter
    • @ConditionalOnMissingBean–>beanFactory中不存在WebMvcConfigurationSupport类型的bean
    • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) –> 加载的优先级为Ordered.HIGHEST_PRECEDENCE + 10 –> Integer.MIN_VALUE + 10
    • @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
      ValidationAutoConfiguration.class }) –> 在DispatcherServletAutoConfiguration, ValidationAutoConfiguration 加载后进行装配。
  2. WebMvcAutoConfiguration中有3个内部类

    1. WebMvcAutoConfigurationAdapter
    2. EnableWebMvcConfiguration
    3. ResourceChainCustomizerConfiguration

    这里有必要说明一下,

    1. WebMvcAutoConfigurationAdapter声明在WebMvcAutoConfiguration中,是为了确保当该类不在类路径下时不会被读取到.
    2. EnableWebMvcConfiguration 该配置类 相当于@EnableWebMvc的功能.

    关于这部分的内容我们在spring boot 源码解析15-spring mvc零配置 中已经解释过了
    WebMvcAutoConfiguration 就是对 spring mvc 进行自动化配置,取代继承WebMvcConfigurerAdapter的方式。

    下面分别对其进行分析.

  3. WebMvcAutoConfigurationAdapter类的继承结构如下:

    《spring boot 源码解析18-WebMvcAutoConfiguration自动化配置揭秘》

    并实现了WebMvcConfigurer接口.这里有必要说明一下:

    spring boot 是如何通过WebMvcAutoConfigurationAdapter和EnableWebMvcConfiguration如何实现自动化的。

    EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration.在DelegatingWebMvcConfiguration中声明了名为configurers,类型为WebMvcConfigurerComposite的属性.其set方法如下:

    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }

    当EnableWebMvcConfiguration加载时,会将beanFactory中所有类型为WebMvcConfigurer的bean都传入该方法,同时包含WebMvcAutoConfigurationAdapter.这样就实现了自动化.

    WebMvcAutoConfigurationAdapter声明了如下注解:

    @Configuration
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    • @Configuration–>配置类
    • @Import(EnableWebMvcConfiguration.class)–> 导入 EnableWebMvcConfiguration配置,当加载该类时,会首先加载EnableWebMvcConfiguration
    • @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })–>可通过spring.mvc.xx,spring.resources.xx进行配置

    WebMvcAutoConfigurationAdapter中有一个内部类–>FaviconConfiguration,当spring.mvc.favicon.enabled等于true时生效(默认生效).其声明了2个bean.

    1. 注册SimpleUrlHandlerMapping,代码如下:

      @Bean
      public SimpleUrlHandlerMapping faviconHandlerMapping() {
          SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
          mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
          mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
                  faviconRequestHandler()));
          return mapping;
      }

      SimpleUrlHandlerMapping的映射规则为:**/favicon.ico–> handler为faviconRequestHandler声明的bean.

    2. ResourceHttpRequestHandler,代码如下:

      @Bean
          public ResourceHttpRequestHandler faviconRequestHandler() {
              ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
              requestHandler
                      .setLocations(this.resourceProperties.getFaviconLocations());
              return requestHandler;
          }

      资源路径如下:

      1. classpath:/META-INF/resources/
      2. classpath:/resources/
      3. classpath:/static/
      4. classpath:/public/
      5. /

      默认是在spring-boot/src/main/resources/下存在,也就是匹配第5个规则

    WebMvcAutoConfigurationAdapter中声明的bean如下:

    1. InternalResourceViewResolver,当beanFactory中不存在InternalResourceViewResolver类型的bean是注册,默认前缀,后缀为null代码如下:

      @Bean
      @ConditionalOnMissingBean
      public InternalResourceViewResolver defaultViewResolver() {
          InternalResourceViewResolver resolver = new InternalResourceViewResolver();
          resolver.setPrefix(this.mvcProperties.getView().getPrefix());
          resolver.setSuffix(this.mvcProperties.getView().getSuffix());
          return resolver;
      }
    2. 注册BeanNameViewResolver,当beanFactory中存在View类型的bean并且不存在BeanNameViewResolver类型的bean时注册.代码如下:

      @Bean
      @ConditionalOnBean(View.class)
      @ConditionalOnMissingBean
      public BeanNameViewResolver beanNameViewResolver() {
          BeanNameViewResolver resolver = new BeanNameViewResolver();
          resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
          return resolver;
      }
    3. 注册ContentNegotiatingViewResolver,当beanFactory中存在ViewResolver的bean并且不存在id为viewResolver,类型为 ContentNegotiatingViewResolver类型的bean时注册.代码如下:

      @Bean
      @ConditionalOnBean(ViewResolver.class)
      @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
      public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
          ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
          // 这里使用在EnableWebMvcConfiguration中配置的ContentNegotiationManager
          resolver.setContentNegotiationManager(
                  beanFactory.getBean(ContentNegotiationManager.class));
          // ContentNegotiatingViewResolver uses all the other view resolvers to locate
          // a view so it should have a high precedence
          resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
          return resolver;
      }
      

      注意,这里使用的是在EnableWebMvcConfiguration中配置的ContentNegotiationManager

    4. 注册LocaleResolver,当beanFactory中不存在LocaleResolver并且spring.mvc.locale 有值时进行注册,代码如下:

      @Bean
      @ConditionalOnMissingBean
      @ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
      public LocaleResolver localeResolver() {
          if (this.mvcProperties
                  .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
              return new FixedLocaleResolver(this.mvcProperties.getLocale());
          }
          AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
          localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
          return localeResolver;
      }

      如果WebMvcProperties中的LocaleResolver为FIXED,则使用FixedLocaleResolver,否则使用AcceptHeaderLocaleResolver.

      默认不配置.

    5. dateFormatter,当 spring.mvc.date-format 有值时注册.代码如下:

      @Bean
      @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
      public Formatter<Date> dateFormatter() {
          return new DateFormatter(this.mvcProperties.getDateFormat());
      }
    6. welcomePageHandlerMapping.代码如下:

      @Bean
      public WelcomePageHandlerMapping welcomePageHandlerMapping(
              ResourceProperties resourceProperties) {
          return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
                  this.mvcProperties.getStaticPathPattern());
      }

      默认拦截路径为/**, 首页为在如下路径下查找:

      • classpath:/META-INF/resources/index.html
      • classpath:/resources/index.html
      • classpath:/static/index.html
      • classpath:/public/index.html
    7. requestContextFilter,当beanFactory中不存在RequestContextListener,RequestContextFilter类型的bean时注册,代码如下:

          @Bean
      @ConditionalOnMissingBean({ RequestContextListener.class,
              RequestContextFilter.class })
      public static RequestContextFilter requestContextFilter() {
          return new OrderedRequestContextFilter();
      }

    同时WebMvcAutoConfigurationAdapter还能对spring mvc做个性化设置,这里我们看下做了哪些配置.

    1. 设置MessageConverter.代码如下:

      public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
          converters.addAll(this.messageConverters.getConverters());
      }
    2. 如果配置了spring.mvc.async.requestTimeout.则设置async的超时.代码如下:

      public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
          Long timeout = this.mvcProperties.getAsync().getRequestTimeout();
          if (timeout != null) {
              // 设置async的超时
              configurer.setDefaultTimeout(timeout);
          }
      }
    3. 设置内容协商属性,代码如下:

      public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
          // 配置媒体映射,默认没配置
          Map<String, MediaType> mediaTypes = this.mvcProperties.getMediaTypes();
          for (Entry<String, MediaType> mediaType : mediaTypes.entrySet()) {
              configurer.mediaType(mediaType.getKey(), mediaType.getValue());
          }
      }

      通过遍历配置的媒体映射,对内容协商进行配置,默认不配置

    4. 如果通过spring.mvc.messageCodesResolverFormat进行配置了值,则实例化DefaultMessageCodesResolver,设置格式为配置的,否则返回null.

    5. addFormatters方法,向FormatterRegistry添加了beanFactory所有Converter,GenericConverter,Formatter类型的bean
    6. addResourceHandlers.代码如下:

      public void addResourceHandlers(ResourceHandlerRegistry registry) {
          // 如果spring.resources.addMappings 为false,则不进行处理
          if (!this.resourceProperties.isAddMappings()) {
              logger.debug("Default resource handling disabled");
              return;
          }
          // 获得缓存时间,默认没配置
          Integer cachePeriod = this.resourceProperties.getCachePeriod();
          if (!registry.hasMappingForPattern("/webjars/**")) {
              // 如果ResourceHandlerRegistry中不包含/webjars/**的路径映射,
              // 则添加 /webjars/** --> classpath:/META-INF/resources/webjars/ 的映射规则
              customizeResourceHandlerRegistration(
                      registry.addResourceHandler("/webjars/**")
                              .addResourceLocations(
                                      "classpath:/META-INF/resources/webjars/")
                      .setCachePeriod(cachePeriod));
          }
          // 获得静态资源的映射路径,默认为 /**
          String staticPathPattern = this.mvcProperties.getStaticPathPattern();
          if (!registry.hasMappingForPattern(staticPathPattern)) {
              // 如果ResourceHandlerRegistry中不包含静态资源的映射路径,
              // 则添加 staticPathPattern --> classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/, classpath:/public/ 的映射规则
              customizeResourceHandlerRegistration(
                      registry.addResourceHandler(staticPathPattern)
                              .addResourceLocations(
                                      this.resourceProperties.getStaticLocations())
                      .setCachePeriod(cachePeriod));
          }
      }
      1. 如果spring.resources.addMappings 为false,则不进行处理.否则进入第2步

      2. 获得缓存时间,可通过spring.resources.cachePeriod 配置.

      3. 如果ResourceHandlerRegistry中不包含/webjars/* 的路径映射,则添加 /webjars/* –> classpath:/META-INF/resources/webjars/ 的映射规则
      4. 如果通过spring.mvc.staticPathPattern 配置的静态资源的映射路径在ResourceHandlerRegistry不存在,则添加 配置映射路径 –>classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/, classpath:/public/ 的映射规则
  4. EnableWebMvcConfiguration类的继承结构如下:

    《spring boot 源码解析18-WebMvcAutoConfiguration自动化配置揭秘》

    其类上只有一个@Configuration表明其是一个配置类.该类是被WebMvcAutoConfigurationAdapter导入的. 因此当ConfigurationClassParser#processConfigurationClass处理时,会调用ConfigurationClass#mergeImportedBy进行合并(因为在解析WebMvcAutoConfigurationAdapter时,首先加载了EnableWebMvcConfiguration的配置)

    1. EnableWebMvcConfiguration中声明的bean方法如下:

      1. requestMappingHandlerAdapter,该方法复写了WebMvcConfigurationSupport的定义.代码如下:

        @Bean
        @Override
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
        // 是否忽略默认视图当重定向时,默认为true
        adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null ? true
                : this.mvcProperties.isIgnoreDefaultModelOnRedirect());
        return adapter;
        }

        调用父类的requestMappingHandlerAdapter,然后设置是否忽略默认视图当重定向时,默认为true.

      2. requestMappingHandlerMapping,该方法复写了WebMvcConfigurationSupport的定义.代码如下:

            @Bean
        @Primary
        @Override
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return super.requestMappingHandlerMapping();
        }

        将父类中声明的RequestMappingHandlerMapping 设置为Primary,这样MvcUriComponentsBuilder才能正确工作

      3. mvcValidator,复写,代码如下:

        @Bean
        @Override
        public Validator mvcValidator() {
        
        if (!ClassUtils.isPresent("javax.validation.Validator",
                getClass().getClassLoader())) {
            return super.mvcValidator();
        }
        return WebMvcValidator.get(getApplicationContext(), getValidator());
        }

        如果不存在javax.validation.Validator,则调用父类,否则,调用WebMvcValidator#get 获取

      4. mvcContentNegotiationManager. 复写,声明如下:

        @Bean
        @Override
        public ContentNegotiationManager mvcContentNegotiationManager() {
        ContentNegotiationManager manager = super.mvcContentNegotiationManager();
        List<ContentNegotiationStrategy> strategies = manager.getStrategies();
        ListIterator<ContentNegotiationStrategy> iterator = strategies.listIterator();
        // 遍历ContentNegotiationManager中配置的ContentNegotiationStrategy,如果ContentNegotiationStrategy是
        // PathExtensionContentNegotiationStrategy的实例,则包装其为OptionalPathExtensionContentNegotiationStrategy
        while (iterator.hasNext()) {
            ContentNegotiationStrategy strategy = iterator.next();
            if (strategy instanceof PathExtensionContentNegotiationStrategy) {
                iterator.set(new OptionalPathExtensionContentNegotiationStrategy(
                        strategy));
            }
        }
        return manager;
        }

        只是在父类的基础上,遍历ContentNegotiationManager中配置的ContentNegotiationStrategy,如果ContentNegotiationStrategy是PathExtensionContentNegotiationStrategy的实例,则包装其为OptionalPathExtensionContentNegotiationStrategy

    2. 在父类WebMvcConfigurationSupport中配置的bean,这里就不在这里赘述了,在spring boot 源码解析15-spring mvc零配置中已经有解释过了

  5. ResourceChainCustomizerConfiguration有如下注解:

    @Configuration
    @ConditionalOnEnabledResourceChain
    • @Configuration –> 配置类
    • @ConditionalOnEnabledResourceChain–> 交由OnEnabledResourceChainCondition进行判断.代码如下:

      @Override
      public ConditionOutcome getMatchOutcome(ConditionContext context,
          AnnotatedTypeMetadata metadata) {
      ConfigurableEnvironment environment = (ConfigurableEnvironment) context
              .getEnvironment();
      // 获得spring.resources.chain.strategy.fixed.enabled 的配置,默认为false
      boolean fixed = getEnabledProperty(environment, "strategy.fixed.", false);
      // 获得spring.resources.chain.strategy.content.enabled 的配置,默认为false
      boolean content = getEnabledProperty(environment, "strategy.content.", false);
      // 获得spring.resources.chain.enabled 的配置,默认为null
      Boolean chain = getEnabledProperty(environment, "", null);
      // match 为null
      Boolean match = ResourceProperties.Chain.getEnabled(fixed, content, chain);
      ConditionMessage.Builder message = ConditionMessage
              .forCondition(ConditionalOnEnabledResourceChain.class);
      // 2.
      if (match == null) {
          // 如果存在 org.webjars.WebJarAssetLocator,则返回匹配
          if (ClassUtils.isPresent(WEBJAR_ASSET_LOCATOR, getClass().getClassLoader())) {
              return ConditionOutcome
                      .match(message.found("class").items(WEBJAR_ASSET_LOCATOR));
          }
          // 否则返回不匹配
          return ConditionOutcome
                  .noMatch(message.didNotFind("class").items(WEBJAR_ASSET_LOCATOR));
      }
      // 3. 如果match,返回匹配,否则返回不匹配
      if (match) {
          return ConditionOutcome.match(message.because("enabled"));
      }
      return ConditionOutcome.noMatch(message.because("disabled"));
      }
      1. 获得spring.resources.chain.strategy.fixed.enabled 的配置,默认为false;获得spring.resources.chain.strategy.content.enabled 的配置,默认为false;获得spring.resources.chain.enabled 的配置,默认为null.调用ResourceProperties.Chain.getEnabled 对match进行赋值.默认为null.
      2. 如果match == null,则 如果存在 org.webjars.WebJarAssetLocator,则返回匹配,否则返回不匹配
      3. 如果match等于true,返回匹配,否则返回不匹配

      因此可知,该配置默认是不进行加载的.

      1. ResourceChainCustomizerConfiguration只有一个被@bean注解的方法,如下:
      @Bean
      public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() {
          return new ResourceChainResourceHandlerRegistrationCustomizer();
      }
  6. 内部类处理完后,由于其有2个被@bean注解的方法,因此会进行加载.如下:

    1. 当beanFactory中不存在HiddenHttpMethodFilter类型的bean时注册,代码如下:

      @Bean
      @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
      public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
      return new OrderedHiddenHttpMethodFilter();
      }
      
    2. 当beanFactory中不存在HttpPutFormContentFilter类型的bean并且spring.mvc.formcontent.putfilter.enabled 等于true.(默认是true)时,进行注册.代码如下:

      @Bean
      @ConditionalOnMissingBean(HttpPutFormContentFilter.class)
      @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
      public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
      return new OrderedHttpPutFormContentFilter();
      }

自动配置总结

WebMvcAutoConfiguration配置生效的条件有2个前提:

  1. web环境
  2. 当前类路径下存在Servlet, DispatcherServlet,WebMvcConfigurerAdapte

下面表格因节省篇幅,就不再列出该前提.

配置的bean声明的类条件
OrderedHiddenHttpMethodFilter(id为hiddenHttpMethodFilter)WebMvcAutoConfigurationbeanFactory中不存在HiddenHttpMethodFilter类型的bean
OrderedHttpPutFormContentFilter(id为httpPutFormContentFilter)WebMvcAutoConfigurationbeanFactory中不存在HttpPutFormContentFilter类型的bean并且spring.mvc.formcontent.putfilter.enabled 等于true.(默认是true)时
ResourceChainResourceHandlerRegistrationCustomizer(id为resourceHandlerRegistrationCustomizer)ResourceChainCustomizerConfiguration默认不配置
RequestMappingHandlerAdapter(id为requestMappingHandlerAdapter)EnableWebMvcConfiguration
InternalResourceViewResolver(id为defaultViewResolver)WebMvcAutoConfigurationAdapterbeanFactory中不存在InternalResourceViewResolver类型的bean
BeanNameViewResolver(id为beanNameViewResolver)WebMvcAutoConfigurationAdapterbeanFactory中存在View类型的bean并且不存在BeanNameViewResolver类型的bean
ContentNegotiatingViewResolver(id为viewResolver)WebMvcAutoConfigurationAdapterbeanFactory中存在ViewResolver的bean并且不存在id为viewResolver,类型为 ContentNegotiatingViewResolver类型的bean
LocaleResolver(id为localeResolver)WebMvcAutoConfigurationAdapterbeanFactory中不存在LocaleResolver并且spring.mvc.locale 有值时进行注册
Formatter(id为dateFormatter)WebMvcAutoConfigurationAdapterspring.mvc.date-format 有值时注册
WelcomePageHandlerMapping(id为welcomePageHandlerMapping)WebMvcAutoConfigurationAdapter
RequestContextFilter(id为requestContextFilter)WebMvcAutoConfigurationAdapterbeanFactory中不存在RequestContextListener,RequestContextFilter类型的bean时
SimpleUrlHandlerMapping(id为faviconHandlerMapping)FaviconConfiguration当spring.mvc.favicon.enabled等于true时生效(默认生效)
ResourceHttpRequestHandler(id为faviconRequestHandler)FaviconConfiguration当spring.mvc.favicon.enabled等于true时生效(默认生效)
RequestMappingHandlerMapping(id为requestMappingHandlerMapping)EnableWebMvcConfiguration
Validator(id为mvcValidator)EnableWebMvcConfiguration
ContentNegotiationManager(id为mvcContentNegotiationManager)EnableWebMvcConfiguration
PathMatcher(id为mvcPathMatcher)WebMvcConfigurationSupport
UrlPathHelper(id为mvcUrlPathHelper)WebMvcConfigurationSupport
UrlPathHelper(id为mvcUrlPathHelper)WebMvcConfigurationSupport
HandlerMapping(id为viewControllerHandlerMapping)WebMvcConfigurationSupport
BeanNameUrlHandlerMapping(id为beanNameHandlerMapping)WebMvcConfigurationSupport
HandlerMapping(id为resourceHandlerMapping)WebMvcConfigurationSupport
ResourceUrlProvider(id为mvcResourceUrlProvider)WebMvcConfigurationSupport
HandlerMapping(id为defaultServletHandlerMapping)WebMvcConfigurationSupport
FormattingConversionService(id为mvcConversionService)WebMvcConfigurationSupport
CompositeUriComponentsContributor(id为mvcUriComponentsContributor)WebMvcConfigurationSupport
HttpRequestHandlerAdapter(id为httpRequestHandlerAdapter)WebMvcConfigurationSupport
SimpleControllerHandlerAdapter(id为simpleControllerHandlerAdapter)WebMvcConfigurationSupport
HandlerExceptionResolver(id为handlerExceptionResolver)WebMvcConfigurationSupport
ViewResolver(id为mvcViewResolver)WebMvcConfigurationSupport
HandlerMappingIntrospector(id为mvcHandlerMappingIntrospector)WebMvcConfigurationSupport
    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/78998669
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞