浅谈Spring Boot关于Springmvc的自动配置

SpringBoot对默认通过WebMvcAutoConfiguration这个类对SpringMVC进行配置。根据官方文档,它实现了以下功能:

1.包含ContentNegotiatingViewResolver和BeanNameViewResolver的视图解析器

通过@Bean注解自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染)

ContentNegotiatingViewResolver是如何解析视图的,下面是在这个类中解析视图的方法。

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;
		}
	}
	...
}
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

	List<View> candidateViews = new ArrayList<>();
                //判断视图解析器是否为空
	if (this.viewResolvers != null) {
		Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
                //遍历视图解析器
	    for (ViewResolver viewResolver : this.viewResolvers) {
                  //根据视图名称,即return的返回值和locale国家化返回视图,如果解析不了,返回null
		    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;
}

ContentNegotiatingViewResolver如何获得解析器,在initServletContext方法中可以发现

@Override
	protected void initServletContext(ServletContext servletContext) {
                //在这里BeanFactoryUtils通过类型判断,在容器中获得ViewResolver类型的解析器
		Collection<ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
		...
	}

这样,就很清晰了,ContentNegotiatingViewResolver通过initServletContext方法组合容器中所有ViewResolver的实现类,再通过resolveViewName解析获得视图。所以,如果想要自定义解析器,只需要实现ViewResolver接口,并通过@Bean放入容器即可,这样SpringBoot就自动会将其组合进来。

 

2.支持提供静态资源,包括对WebJars的支持

在WebMvcAutoConfiguration中有这样一个方法

public void addResourceHandlers(ResourceHandlerRegistry registry) {
	...
	Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
	CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                        //第一种静态资源:如果资源注册器中不包含/webjars/**映射,则添加
	if (!registry.hasMappingForPattern("/webjars/**")) {
	    customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
                         //第二种静态资源:获取静态资源文件映射      
	String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                        //如果不包含,则添加,具体看下面分析
	if (!registry.hasMappingForPattern(staticPathPattern)) {
	    customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
						.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
	}
}
  • 所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源。webjars:以jar包的方式引入静态资源。
  • "/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射,this.resourceProperties.getStaticLocations()

如果想自定义静态资源位置,也可以在配置文件配置

  • 欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
	return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext),                      
	applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}
private Optional<Resource> getWelcomePage() {
          //获取静态资源文件地址,即"classpath:/META-INF/resources/",
	//"classpath:/resources/", "classpath:/static/", "classpath:/public/"
	String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
        //在这些文件地址下寻找
	return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
                //在资源文件地址下寻找index.html
	return this.resourceLoader.getResource(location + "index.html");
}
  • 所有的 **/favicon.ico 都是在静态资源文件下找;
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
	private final ResourceProperties resourceProperties;
	private ResourceLoader resourceLoader;
	...
	@Bean
	public SimpleUrlHandlerMapping faviconHandlerMapping() {
		SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
		mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
                //设置图标的资源映射
		mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
		return mapping;
	}
	@Bean
	public ResourceHttpRequestHandler faviconRequestHandler() {
		ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
                //设置路径地址
		requestHandler.setLocations(resolveFaviconLocations());
		return requestHandler;
	}
        private List<Resource> resolveFaviconLocations() {
                //获取路径地址集合,即"classpath:/META-INF/resources/",
		//"classpath:/resources/", "classpath:/static/", "classpath:/public/"
	    String[] staticLocations = getResourceLocations(this.resourceProperties.getStaticLocations());
	    List<Resource> locations = new ArrayList<>(staticLocations.length + 1)
  Arrays.stream(staticLocations).map(this.resourceLoader::getResource).forEach(locations::add);
	    locations.add(new ClassPathResource("/"));
	    return Collections.unmodifiableList(locations);
	}
}

 

3.自动注册Converter , GenericConverter , Formatter beans.

  • Converter:转换器; public String hello(User user):类型转换使用Converter
  • Formatter :格式化器; 2017.12.17 => Date;
@Bean
@Override
public FormattingConversionService mvcConversionService() {
        //创建一个转换服务类,根据配置的转换格式
	WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
        //将转换服务器添加到注册器种
	addFormatters(conversionService);
	return conversionService;
}
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
	/**
	 * Date format to use. For instance, `dd/MM/yyyy`.
	 */
	private String dateFormat;
    ...
}

可以在配置文件配置转换格式。

@Override
public void addFormatters(FormatterRegistry registry) {
    //根据类型获取Converter类型组件集合,遍历添加
	for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
		registry.addConverter(converter);
	}
    //根据类型获取GenericConverter类型组件集合,遍历添加
	for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
		registry.addConverter(converter);
	}
    //根据类型获取Formatter类型组件集合,遍历添加
	for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
		registry.addFormatter(formatter);
	}
}

所以,自己添加的格式化器转换器,我们只需要放在容器中即可。

 

4.Support for HttpMessageConverters (see below).

HttpMessageConverter:SpringMVC用来转换Http请求和响应的;User ---> Json;

这个messageConvertersProvider因为在构造函数中,所以会去IOC容器中获取,自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中 (@Bean,@Component)

 

5.Automatic registration of MessageCodesResolver (see below).

public MessageCodesResolver getMessageCodesResolver() {
    //从mvcProperties中获取MessageCodesResolverFormat
    if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
		DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
                resolver.setMessageCodeFormatter(this.mvcProperties
                                .getMessageCodesResolverFormat());
		return resolver;
	}
	return null;
}

所以可以看到,它是在定义错误代码生成规则。

 

6.Automatic use of a ConfigurableWebBindingInitializer bean (see below).

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
	try {
		return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);
	}
	catch (NoSuchBeanDefinitionException ex) {
		return super.getConfigurableWebBindingInitializer();
	}
}

可以看到,它是从容器中获取ConfigurableWebBindingInitializer类型组件的,如果没有这样的组件,就调用父类方法初始化一个

protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
	ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
	initializer.setConversionService(mvcConversionService());
	initializer.setValidator(mvcValidator());
	MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
	if (messageCodesResolver != null) {
		initializer.setMessageCodesResolver(messageCodesResolver);
	}
	return initializer;
}

ConfigurableWebBindingInitializer的作用是将请求数据绑定到相关JavaBean;在其类中有一个initBinder方法就是来初始化绑定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值