我们自己分析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,我们这些拦截器