前言
上篇文章,我们分析了spring boot 中其他有关mvc的自动化配置类,只剩下WebMvcAutoConfiguration没有解析,这篇文章对其进行收尾
解析
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 加载后进行装配。
WebMvcAutoConfiguration中有3个内部类
- WebMvcAutoConfigurationAdapter
- EnableWebMvcConfiguration
- ResourceChainCustomizerConfiguration
这里有必要说明一下,
- WebMvcAutoConfigurationAdapter声明在WebMvcAutoConfiguration中,是为了确保当该类不在类路径下时不会被读取到.
- EnableWebMvcConfiguration 该配置类 相当于@EnableWebMvc的功能.
关于这部分的内容我们在spring boot 源码解析15-spring mvc零配置 中已经解释过了
WebMvcAutoConfiguration 就是对 spring mvc 进行自动化配置,取代继承WebMvcConfigurerAdapter的方式。下面分别对其进行分析.
WebMvcAutoConfigurationAdapter类的继承结构如下:
并实现了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.
注册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.
ResourceHttpRequestHandler,代码如下:
@Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler .setLocations(this.resourceProperties.getFaviconLocations()); return requestHandler; }
资源路径如下:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
- /
默认是在spring-boot/src/main/resources/下存在,也就是匹配第5个规则
WebMvcAutoConfigurationAdapter中声明的bean如下:
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; }
注册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; }
注册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
注册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.
默认不配置.
dateFormatter,当 spring.mvc.date-format 有值时注册.代码如下:
@Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat()); }
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
requestContextFilter,当beanFactory中不存在RequestContextListener,RequestContextFilter类型的bean时注册,代码如下:
@Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) public static RequestContextFilter requestContextFilter() { return new OrderedRequestContextFilter(); }
同时WebMvcAutoConfigurationAdapter还能对spring mvc做个性化设置,这里我们看下做了哪些配置.
设置MessageConverter.代码如下:
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.addAll(this.messageConverters.getConverters()); }
如果配置了spring.mvc.async.requestTimeout.则设置async的超时.代码如下:
public void configureAsyncSupport(AsyncSupportConfigurer configurer) { Long timeout = this.mvcProperties.getAsync().getRequestTimeout(); if (timeout != null) { // 设置async的超时 configurer.setDefaultTimeout(timeout); } }
设置内容协商属性,代码如下:
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()); } }
通过遍历配置的媒体映射,对内容协商进行配置,默认不配置
如果通过spring.mvc.messageCodesResolverFormat进行配置了值,则实例化DefaultMessageCodesResolver,设置格式为配置的,否则返回null.
- addFormatters方法,向FormatterRegistry添加了beanFactory所有Converter,GenericConverter,Formatter类型的bean
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)); } }
如果spring.resources.addMappings 为false,则不进行处理.否则进入第2步
获得缓存时间,可通过spring.resources.cachePeriod 配置.
- 如果ResourceHandlerRegistry中不包含/webjars/* 的路径映射,则添加 /webjars/* –> classpath:/META-INF/resources/webjars/ 的映射规则
- 如果通过spring.mvc.staticPathPattern 配置的静态资源的映射路径在ResourceHandlerRegistry不存在,则添加 配置映射路径 –>classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/, classpath:/public/ 的映射规则
EnableWebMvcConfiguration类的继承结构如下:
其类上只有一个@Configuration表明其是一个配置类.该类是被WebMvcAutoConfigurationAdapter导入的. 因此当ConfigurationClassParser#processConfigurationClass处理时,会调用ConfigurationClass#mergeImportedBy进行合并(因为在解析WebMvcAutoConfigurationAdapter时,首先加载了EnableWebMvcConfiguration的配置)
EnableWebMvcConfiguration中声明的bean方法如下:
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.
requestMappingHandlerMapping,该方法复写了WebMvcConfigurationSupport的定义.代码如下:
@Bean @Primary @Override public RequestMappingHandlerMapping requestMappingHandlerMapping() { return super.requestMappingHandlerMapping(); }
将父类中声明的RequestMappingHandlerMapping 设置为Primary,这样MvcUriComponentsBuilder才能正确工作
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 获取
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
在父类WebMvcConfigurationSupport中配置的bean,这里就不在这里赘述了,在spring boot 源码解析15-spring mvc零配置中已经有解释过了
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")); }
- 获得spring.resources.chain.strategy.fixed.enabled 的配置,默认为false;获得spring.resources.chain.strategy.content.enabled 的配置,默认为false;获得spring.resources.chain.enabled 的配置,默认为null.调用ResourceProperties.Chain.getEnabled 对match进行赋值.默认为null.
- 如果match == null,则 如果存在 org.webjars.WebJarAssetLocator,则返回匹配,否则返回不匹配
- 如果match等于true,返回匹配,否则返回不匹配
因此可知,该配置默认是不进行加载的.
- ResourceChainCustomizerConfiguration只有一个被@bean注解的方法,如下:
@Bean public ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer() { return new ResourceChainResourceHandlerRegistrationCustomizer(); }
内部类处理完后,由于其有2个被@bean注解的方法,因此会进行加载.如下:
当beanFactory中不存在HiddenHttpMethodFilter类型的bean时注册,代码如下:
@Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); }
当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个前提:
- web环境
- 当前类路径下存在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 | 无 |