SpringBoot_web开发-SpringMVC自动配置原理

我们自己分析Springboot的源码,里面有什么功能,第二个我们来参照官方文档,官方文档里面写了什么功能,我们看

官方文档告诉我们自动配置了哪些,再来带大家来翻一翻源码,希望通过这一次的分析,后来的模块原理都是一样的,

我们打开这个文档

https://docs.spring.io/spring-boot/docs/1.5.22.RELEASE/reference/html/

有一个开发WEB应用Developing web applications,这里是说了我们怎么来使用SpringMVC,SpringMVC的自动配置都在这,

https://docs.spring.io/spring-boot/docs/1.5.22.RELEASE/reference/html/
boot-features-developing-web-applications.html

我们来看一下,首先如果我们要用SpringMVC,就直接上手用就行了,所有的东西都自动配置好了,我们可以非常简单的使用SpringMVC

应用,那他到底用了哪些自动配置呢,这里就是SpringBoot对SpringMVC配置的精髓

我们跟着官方文档慢慢的来分析,Springboot提供SpringMVC要工作时,大多数场景的自动配置,那就一句话,

自动配置好了SpringMVC,都配好了哪些,下面有列举,以下是SpringBoot的默认配置,我们要对SpringMVC的原理

掌握的比较清楚,如果这一块还不清楚的,那我就来给大家一个一个来分析一下,他说我们自动配置,

ContentNegotiatingViewResolver和BeanNameViewResolver,就是自动配置了视图解析器,而这个视图解析器呢,

在SpringMVC中,功能就是根据我们方法的返回值,我们得到视图对象,视图对象就是Viewer对象,视图对象绝对是

转发还是重定向,视图对象决定如何渲染,所谓的渲染,是否要转发到页面,还是重定向到页面,我们再来看一下他的

底层原理,我们打开WebMvcAutoConfiguration,SpringMVC的自动配置,

我们看一下有没有这个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;
}

这一块确实有这个配置,@Bean给我们容器中添加这个组件,添加了这个ContentNegotiatingViewResolver,

这个ViewResolver的功能,既然他是视图解析器,他就要解析视图,他怎么解析视图呢,获取候选的视图对象,

然后选择一个最适合的视图对象,然后把这个视图对象返回,而它怎么样选择视图对象呢,他其实是把所有视图解析器拿来,

挨个来解析,ContentNegotiatingViewResolver它是组合所有的视图解析器的,那既然是这样,我们来看一下他的组合逻辑,

ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();

他先new了一个ContentNegotiatingViewResolver,他要用到这些视图解析器,它是从哪拿的呢,

我们来看一下视图解析器,这里有一个初始化方法,

它是用BeanFactory工具,从容器中获取所有的视图解析器,把这个视图解析器作为所有的视图解析器了,如果我们要写视图解析器,

我们只需要知道如何定制,我们可以自己给容器中添加一个视图解析器,ContentNegotiatingViewResolver就会将其自动的组合进来,

是不是我们分析的这样呢,可以给大家来测试一下,比如我们在主类里边,myViewResolver还没有,我在这里就写一个静态的内部类,

既然是视图解析器,那我们要实现ViewResolver接口,然后添加未实现的方法,那这个方法我就不实现了,

我主要来new一个MyViewResolver,

我们看有没有起作用就行了,怎么看起作用呢,我们来看DispatcherServlet,请求一进来就会来到doDispatch方法,我们在这里

打一个断点,看一下视图解析器是什么,我以debug的方式来运行,启动起来以后,我们随便来访问一个请求,

我们的确实进来了,我们就是给容器中添组件就行了,它自动就加进来了,后面的逻辑基本上都一样了,说明我们的这个猜想是正确的
我们以后看东西,就看关键字,不用分析的那么细节,支持静态资源的,静态资源首页,包括favicon,这是静态首页访问的,

静态资源文件夹路径,webjars,之前都来用过,我们就不说了,自动注册了Converter这些东西,什么Formatter,Converter

翻译过来就是转换器,假设有一个方法public String hello(User user),我们发请求来到这个方法,如果页面的数据和User

里的属性正好一一对应,SpringMVC是不是要自动封装,但是在自动封装期间,一定会出现类型转换问题,而页面提交的数据都是

文本,所以我们要把18转成Integer类型,true也是文本,要转成布尔类型,我们类型转换要用Converter组件,而对应的还有一个

Formatter,这个就叫格式化器,比如页面带来一个数据2017年,12月17号,我们就要对应转成一个日期类型,所以这就牵涉到两个

方面,一个是把字符串转成日期类型,第二种要按照这种格式转过来,因为有些国家写法不一样,所以我们要按照一定的格式转过来,

他也自动注册了,自动注册的在哪呢,

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

日期格式调整,我们用spring.mvc.date-format来调整,他确实注册了格式化器,而注册的条件,他在这注册的条件是什么呢,

如果我们配了日期格式化,他就会注到格式化器,如果我们没配,我们就不注册了,在配置文件中要配置日期要用哪种方式,

在文件中配置日志格式化的规则,而我们配了这个以后呢,加一个DateFormatter日期格式化对象,那如果我们没配,自然就没有了,

当然要注意,下面还有一个方法,addFormatters

@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);
	}
}

添加格式化器,格式化器里要加一些注册,东西添的时候怎么添呢,叫getBeansOfType,这个方法就在他的下边,

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

他拿到beanFactory,也就是我们的容器,从容器中获得所有的Converter,然后把它们挨个遍历迁过去,

registry.addConverter(converter);

又看到这个关键字,又得出一个结论,我们自己想要做,能不能做,自己添加的格式化器,包括转换器,我们只需要放在容器

中即可,这个东西我们就不用像以前那样测试了,添进来肯定能用,他还帮我们添加了HttpMessageConverters功能,

我得说一下MessageConverters,他的作用是什么呢,MessageConverters叫消息转换器,这是SpringMVC用来转换Http请求和

响应的,举一个例子,比如我们有一个方法,返回了User,我想让他以JSON的形式写出去,那我们就得有一个使User以JSON的

形式写出去的MessageConverters,而MessageConverters是怎么在容器里面配的呢,来搜索一下HttpMessageConverters,

private final HttpMessageConverters messageConverters;

这个也是在@Configuration类中,配置类和之前的写法一样,我们这个大配置类中有一个小配置类,这个配置类只有一个

有参构造器,

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();
}

传入messageConverters,而且这里标了@Lazy,@Lazy是懒加载,用的时候才会自动注入进来,而这个messageConverters,是什么

大家来注意,如果只有一个有参构造器的情况下,每一个参数的值都是要从容器中拿的,相当于HttpMessageConverters怎么确定值,

是从容器中确定的,确定什么呢,我们来看一下messageConverters,

public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {

他其实是一个Iterable<HttpMessageConverter<?>>,是HttpMessageConverter的数组或者集合,需要从容器中获取所有的

messageConverters,获取所有的HttpMessageConverter,也就是如果我们要自己来获取一个HttpMessageConverter,

我们还是那句话,自己给容器中添加HttpMessageConverter,只需要将自己的组件放到容器中,我们就注册在容器中,用

@Bean的方法,或者@Component,反正你把它扫描到容器里面都行,那是不是这样呢,也可以看一下官方文档,有做JSON的,

有做XML的,但是如果我们自己想要用一个,怎么办呢,而且是我们自己添加一个,他来new一个HttpMessageConverter,放到

容器中,我们给容器中添加就可以了

就是我们这个的自动配置,接下来的两个自动配置我们来看看就行了,一个叫MessageCodesResolver,我们

看一下这是什么东西呢,看有没有人配他,

@Override
public MessageCodesResolver getMessageCodesResolver() {
	if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
		DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
		resolver.setMessageCodeFormatter(
				this.mvcProperties.getMessageCodesResolverFormat());
		return resolver;
	}
	return null;
}

他能从一个配置类里哪一个配置,我们不看这个组件了,我们来看这个配置,

/**
 * Common message code formats.
 * @see MessageCodeFormatter
 * @see DefaultMessageCodesResolver#setMessageCodeFormatter(MessageCodeFormatter)
 */
public enum Format implements MessageCodeFormatter {

	/**
	 * Prefix the error code at the beginning of the generated message code. e.g.:
	 * {@code errorCode + "." + object name + "." + field}
	 */
	PREFIX_ERROR_CODE {
		@Override
		public String format(String errorCode, String objectName, String field) {
			return toDelimitedString(errorCode, objectName, field);
		}
	},

	/**
	 * Postfix the error code at the end of the generated message code. e.g.:
	 * {@code object name + "." + field + "." + errorCode}
	 */
	POSTFIX_ERROR_CODE {
		@Override
		public String format(String errorCode, String objectName, String field) {
			return toDelimitedString(objectName, field, errorCode);
		}
	};

它里面是这种Format,定义错误代码的生成规则的,比如我们在做JSR303数据校验的时候,某一个字段发生错误

以后,是怎么样产生错误代码的,这里一个错误代码,对象名,属性名,这是第一种规则,第二种是对象名+属性名+错误代码,

这是定义错误代码生成规则的,知道一下就行了,定义错误代码生成规则的,我们来看一下ConfigurableWebBindingInitializer,

我们先搜一下他在哪里有这个配置,

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

我们别的不看,我们看这里有一句话叫什么,beanFactory,从容器中拿这个东西,我们相当于也可以来配一个,来替换默认的,

因为它是从容器中拿的,我们也只需要从容器中添一个组件,添加到容器中,那这个是什么作用呢,我们看一下这段代码,

而它是从容器中拿,拿不到它会调用super,super的这个方法,

/**
 * Return the {@link ConfigurableWebBindingInitializer} to use for
 * initializing all {@link WebDataBinder} instances.
 */
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer() {
	ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
	initializer.setConversionService(mvcConversionService());
	initializer.setValidator(mvcValidator());
	initializer.setMessageCodesResolver(getMessageCodesResolver());
	return initializer;
}

帮我们来创建一个Initializer,而这个Initializer的作用呢,来初始化我们的WEB数据绑定器,WEB数据绑定器的功能,

就是把请求数据绑定到JavaBean中,我们发过来的请求带了几个参数,我们要封装到我们的User对象里边,那我们就需要

用到我们的数据绑定器,牵涉到数据类型格式化等等,会用到前面的组件,这都是SpringMVC底层的原理,包括

ConfigurableWebBindingInitializer,他这里有一个方法,initBinder

@Override
public void initBinder(WebDataBinder binder, WebRequest request) {
	binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
	if (this.directFieldAccess) {
		binder.initDirectFieldAccess();
	}
	if (this.messageCodesResolver != null) {
		binder.setMessageCodesResolver(this.messageCodesResolver);
	}
	if (this.bindingErrorProcessor != null) {
		binder.setBindingErrorProcessor(this.bindingErrorProcessor);
	}
	if (this.validator != null && binder.getTarget() != null &&
			this.validator.supports(binder.getTarget().getClass())) {
		binder.setValidator(this.validator);
	}
	if (this.conversionService != null) {
		binder.setConversionService(this.conversionService);
	}
	if (this.propertyEditorRegistrars != null) {
		for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
			propertyEditorRegistrar.registerCustomEditors(binder);
		}
	}
}

就是用来初始化WEB数据绑定,当然我们的核心就在这一块,要想放,我们给容器中放一个就行了,但是我们自动配置的功能,

可不止有这么多,是分析了WebMvcAutoConfiguration这个类,然后Springboot对我们整个WEB的配置呢,我们都可以来到

这个包里面,我们有一个autoconfig,我们说这个自动配置,这里面用的组件功能可能不是很了解,

org.springframework.boot.autoconfigure.web

web所有自动配置场景,但是希望能够通过说自动配置,大家提供一种模式和思想,什么思想呢,我们如何修改这个默认配置呢,

首先这些都是一个统一的一个模式,我总结的这个模式呢,不只是用于WEB模块,其他模块都这样,那我们想修改默认配置的

第一个配置,看我们之前,我们想要添格式化器,放容器中就行了,我们要添MessagerConverter,我们放容器中就行了,

好多就是给容器中放一个就行了,因为SpringBoot大量用到了这个细节,比如在WebMvc里边,我们想给容器中添加

HiddenHttpMethodFilter,这个是支持REST风格的过滤器

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}

但是他要给容器中注册这个bean,他怎么做呢,它是这样的,@ConditionalOnMissingBean,先判断容器中如果没有

这个组件,没有这个类型的组件,然后他就给容器中new一个放到容器中,我们还是之前说的那句话,只要这个组件在容器中了,

SpringBoot就能想办法让他生效,至于是怎么生效的,我们后来可以慢慢来体会,第一个模式就是,SpringBoot在用很多组件的

时候,在自动配置很多组件的时候,都有一个模式,先看容器中有没有用户自己配置,如果我们用户用@Bean了,或者@Component,

扫描了一个相关的组件,已经进入容器中了,如果有就用用户配置了,如果没有呢,才自动配置,好多代码都是这样,包括我们容器中的

组件,如果说有些组件要确定多个,他把用户配置的组件,会和他默认的合并起来,如果有些组件可以有多个,比如我们的视图解析器,

ViewResolver,将用户配置的,和自己默认的,组合起来,所以这就是第一种模式,我们想要修改默认配置,那在很多情况下,我们给容器中

添加一个组件就行了,Springboot就会优先使用我们的,这不是所有的情况,比如人家这段话就说,

If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration 

(interceptors, formatters, view controllers etc.) you can add your own @Configuration class of 

type WebMvcConfigurerAdapter,but without @EnableWebMvc. If you wish to provide custom instances of 

RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver 

you can declare a WebMvcRegistrationsAdapter instance providing such components.

我们还想添加一些额外的功能特性,比如我们要填View Controller,我们这些拦截器

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值