SpringMvc自动配置原理

基于springboot1.5.19源码分析。

首先看看springboot参考手册是怎么说的:

29.1.1 Spring MVC Auto-configuration

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (covered later in this document)).
  • Automatic registration of ConverterGenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • Automatic registration of MessageCodesResolver (covered later in this document).
  • Static index.html support.
  • Custom Favicon support (covered later in this document).
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMappingRequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

springboot为springmvc大多数的应用场景提供了自动配置。

自动配置添加了下面一些特点在spring的默认配置之上:

包含ContentNegotiatingViewResolver和BeanNameViewResolver这两个bean。

1、ContentNegotiatingViewResolver

打开WebMvcAutoConfiguration类,在类中搜索ContentNegotiatingViewResolver:

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
   ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
   resolver.setContentNegotiationManager(
         beanFactory.getBean(ContentNegotiationManager.class));
   // 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;
}

最上面的两个@Conditional注解表示在ViewResolver类被注册且没有注册过ContentNegotiatingViewResolver组件这个bean注册才会生效。方法第一行new了一个ContentNegotiatingViewResolver对象,我们点进去,在这个视图解析器类中,我们可以找到resolveViewName这个视图解析方法:

@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
   RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
   Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
   List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
   if (requestedMediaTypes != null) {
      //获取所有候选的解析视图
      List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
      //获取最合适的视图
      View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
      if (bestView != null) {
         return bestView;
      }
   }
   if (this.useNotAcceptableStatusCode) {
      if (logger.isDebugEnabled()) {
         logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
      }
      return NOT_ACCEPTABLE_VIEW;
   }
   else {
      logger.debug("No acceptable view found; returning null");
      return null;
   }
}

我们进入getCandidateViews这个方法:

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<View>();
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				candidateViews.add(view);
			}
			for (MediaType requestedMediaType : requestedMediaTypes) {
				List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
				for (String extension : extensions) {
					String viewNameWithExtension = viewName + '.' + extension;
					view = viewResolver.resolveViewName(viewNameWithExtension, locale);
					if (view != null) {
						candidateViews.add(view);
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}

可以看到这些候选的视图解析器来自于this.viewResolvers,为了找到这个值,我们在当前类中找到了initServletContext这个方法:

	@Override
	protected void initServletContext(ServletContext servletContext) {
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();
		if (this.viewResolvers == null) {
			this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.size());
			for (ViewResolver viewResolver : matchingBeans) {
				if (this != viewResolver) {
					this.viewResolvers.add(viewResolver);
				}
			}
		}
		else {
			for (int i = 0; i < viewResolvers.size(); i++) {
				if (matchingBeans.contains(viewResolvers.get(i))) {
					continue;
				}
				String name = viewResolvers.get(i).getClass().getName() + i;
				getApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolvers.get(i), name);
			}

		}
		if (this.viewResolvers.isEmpty()) {
			logger.warn("Did not find any ViewResolvers to delegate to; please configure them using the " +
					"'viewResolvers' property on the ContentNegotiatingViewResolver");
		}
		AnnotationAwareOrderComparator.sort(this.viewResolvers);
		this.cnmFactoryBean.setServletContext(servletContext);
	}

 

看这个方法,viewResolvers这个list数组的值来自于matchingBeans,而matchingBeans的值来自于

Collection<ViewResolver> matchingBeans =
      BeanFactoryUtils.beansOfTypeIncludingAncestors(getApplicationContext(), ViewResolver.class).values();

这个方法就是从spring容器中获取所有的视图解析器,也就是说我们可以写一个自定义的视图解析器只要实现ViewResovler接口,自定义的视图解析器也会被注入到spring容器中,测试一下:

自定义视图解析器:

@Configuration
public class ApplicationConfig {

    public class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            return null;
        }
    }

    /**
     * 自定义视图解析器
     */
    @Bean
    @ConditionalOnMissingBean(MyViewResolver.class)
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }
}

启动项目,在DispatcherServlet类的doDispatch方法打上断点,发送请求,看自定义的视图解析器是否生效:

 

2、静态资源映射

Support for serving static resources, including support for WebJars (covered later in this document)).

关于静态资源映射可以看我的另一篇文章:springboot对静态资源的映射规则

 

3、转换器和格式化器

Automatic registration of ConverterGenericConverter, and Formatter beans.

自动注册了转换器和格式化器的组件。

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

日期格式化器,只有在配置文件中配置了spring.mvc.date-format的值才会注册该组件。

		@Override
		public void addFormatters(FormatterRegistry registry) {
			for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
				registry.addConverter(converter);
			}
			for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
				registry.addConverter(converter);
			}
			for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
				registry.addFormatter(formatter);
			}
		}

		private <T> Collection<T> getBeansOfType(Class<T> type) {
			return this.beanFactory.getBeansOfType(type).values();
		}

ConverterGenericConverter, and Formatter这三种组件都是从容器中获取的,我们如果想进行拓展就可以实现对应的接口就行了。

 

4、HttpMessageConverters 消息转换器

  • Support for HttpMessageConverters (covered later in this document).
  • 支持HttpMessageConverters(本文档稍后介绍)。

HttpMessageConverter:springmvc用来转换http请求和响应,比如return对象,json形式响应
 

		public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
				WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
				@Lazy HttpMessageConverters messageConverters,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider) {
			this.resourceProperties = resourceProperties;
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConverters = messageConverters;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider
					.getIfAvailable();
		}
	public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
		this(Arrays.asList(additionalConverters));
	}


	public HttpMessageConverters(
			Collection<HttpMessageConverter<?>> additionalConverters) {
		this(true, additionalConverters);
	}

从容器中获取所有的HttpMessageConverter。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值