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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

上篇文章,我们分析了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类的继承结构如下:

    20191017100465\_1.png

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

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

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

        @Autowired(required = false)
        public void setConfigurers(List 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 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> 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 mediaTypes = this.mvcProperties.getMediaTypes();
              for (Entry 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类的继承结构如下:

    20191017100465\_2.png

    其类上只有一个@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 strategies = manager.getStrategies();
            ListIterator 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) WebMvcAutoConfiguration beanFactory中不存在HiddenHttpMethodFilter类型的bean
OrderedHttpPutFormContentFilter(id为httpPutFormContentFilter) WebMvcAutoConfiguration beanFactory中不存在HttpPutFormContentFilter类型的bean并且spring.mvc.formcontent.putfilter.enabled等于true.(默认是true)时
ResourceChainResourceHandlerRegistrationCustomizer(id为resourceHandlerRegistrationCustomizer) ResourceChainCustomizerConfiguration 默认不配置
RequestMappingHandlerAdapter(id为requestMappingHandlerAdapter) EnableWebMvcConfiguration
InternalResourceViewResolver(id为defaultViewResolver) WebMvcAutoConfigurationAdapter beanFactory中不存在InternalResourceViewResolver类型的bean
BeanNameViewResolver(id为beanNameViewResolver) WebMvcAutoConfigurationAdapter beanFactory中存在View类型的bean并且不存在BeanNameViewResolver类型的bean
ContentNegotiatingViewResolver(id为viewResolver) WebMvcAutoConfigurationAdapter beanFactory中存在ViewResolver的bean并且不存在id为viewResolver,类型为ContentNegotiatingViewResolver类型的bean
LocaleResolver(id为localeResolver) WebMvcAutoConfigurationAdapter beanFactory中不存在LocaleResolver并且spring.mvc.locale有值时进行注册
Formatter(id为dateFormatter) WebMvcAutoConfigurationAdapter spring.mvc.date-format有值时注册
WelcomePageHandlerMapping(id为welcomePageHandlerMapping) WebMvcAutoConfigurationAdapter
RequestContextFilter(id为requestContextFilter) WebMvcAutoConfigurationAdapter beanFactory中不存在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

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析18-WebMvcAutoConfiguration自动化配置揭秘

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏