5. MVC自动配置原理
5.1 官网阅读
在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。
只有弄清楚了这一切,我们才能在未来更好地适应它。方式一:源代码分析,方式二:官方文档!
[En]
Only by figuring all this out will we be more comfortable with it in the future. Way one: source code analysis, way two: official documents!
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring's defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
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).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
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 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
5.2 内容协商视图解析器
自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们找到 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
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
我们可以点进 ContentNegotiatingViewResolver这个类,找到对应的解析视图的代码
@Override
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// ...
}
我们继续点getCandidateViews进去看,他是怎么获得候选的视图的呢?
getCandidateViews中看到他是把所有的视图解析器拿来,进行循环,挨个解析!
private List getCandidateViews(String viewName, Locale locale, List requestedMediaTypes)
throws Exception {
List candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List 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 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!
继续在ContentNegotiatingViewResolver类中搜索initServletContext
@Override
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
Collection matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
// ...
}
既然在容器中找到了一个视图解析器,我们能猜到我们可以自己实现一个视图解析器吗?
[En]
Since it is in the container to find a view parser, can we guess that we can implement a view parser ourselves?
我们可以自己向容器添加一个视图解析器,这个类将帮助我们自动组合它!
[En]
We can add a view parser to the container ourselves, and this class will help us combine it automatically!
5.3 转换器和格式化器
在 WebMvcConfigurationSupport类中找到格式化转换器 FormattingConversionService
@Bean
@Override
public FormattingConversionService mvcConversionService() {
// 拿到配置文件中的格式化规则
WebConversionService conversionService =
new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
// @Bean
// public FormattingConversionService mvcConversionService() {
// // 拿到配置文件中的格式化规则
// FormattingConversionService conversionService = new DefaultFormattingConversionService();
// addFormatters(conversionService);
// return conversionService;
// }
点击去getDateFormat(),发现默认的格式为”dd/MM/yyyy”
public String getDateFormat() {
return this.dateFormat;
}
/**
* Date format to use. For instance, dd/MM/yyyy
. 默认的
*/
private String dateFormat;
可以看到在我们的Properties文件中,我们可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
# 时间日期格式化
spring.mvc.date-format=yyyy-MM-dd

5.4 修改SpringBoot的默认配置
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论,这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码,得出结论!
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置默认的。
如果可以有多个组件,则将用户配置与您自己的默认设置结合起来,例如我们的视图解析器!
[En]
If there can be more than one component, combine the user configuration with your own default, such as our view parser!
扩展使用SpringMVC 官方文档如下:
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 RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer, 还不能标注@EnableWebMvc注解
我们去自己写一个,新建一个 config包,并在该包下写一个类 MyMvcConfig
package com.dzj.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//如果想自定义一些功能,只要写这个组件,然后将它交给SpringBoot,SpringBoot就会帮我们自动装配
//扩展mvc DispatcherServlet
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/index.html ,就会跳转到index页面;
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
}
所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理:
1、WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
2、这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
3、我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
// ...
}
4、我们可以在 DelegatingWebMvcConfiguration这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
5、我们点进去看一下
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
5.5 全面接管SpringMVC
官方文档:
If you want to take complete control of Spring MVC
you can add your own @Configuration annotated with @EnableWebMvc.
接管方法:只需在我们的配置类上要加一个注解 @EnableWebMvc
全面接管即SpringBoot对SpringMVC的自动配置就不再生效了,所有的都是我们自己配置!
我们开发中,不推荐使用全面接管SpringMVC
在想什么?为什么要添加评论,自动配置无效!请看一下源代码:
[En]
Thinking? Why add a comment, the automatic configuration is invalid! Take a look at the source code:
1、点击进入这个注解@EnableWebMvc,这里发现它是导入了一个类,我们可以继续进去看
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
2、它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
3、我们来回顾一下Webmvc自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 这个注解的意思就是:容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
Original: https://www.cnblogs.com/aadzj/p/15636632.html
Author: 小公羊
Title: SpringBoot-MVC自动配置原理
相关阅读
Title: HTTP状态码1XX深入理解
前段时间,我看了《御礼》,里面有很多细节,很有思想。读过的一些评论是:终于在剧中看到了真正的事业,第一次付出了偿还普通人的钱。让我印象深刻的是只有皇室才能吃的葡萄。我认为这是非常合理的。剧中所说的清清楚楚,在中晚唐唐玄宗时代,丝绸之路刚刚打通,西域(今新疆以北)的葡萄终于被吃掉了,对应的是整个历史时期。
[En]
Some time ago, I saw “Imperial gifts”, in which there are a lot of details very thoughtful. Read some comments are: finally in the play to see the real career, paid the first time to pay back the money of the normal people. What impresses me is the grapes that can only be eaten by the royal family. I think it’s very reasonable. What is said in the play is clear and clear, at the time of Tang Xuanzong in the middle and late Tang Dynasty, when the Silk Road was just opened, and grapes from the Western regions (now Xinjiang and beyond) were finally eaten, which corresponds to that whole period of history.
谈到对应的问题,咱们回到正题,http状态码1XX。对于http状态200、404、500,大家可能熟悉一些。1XX可能实际中从来没有见过,今天咱们用剥洋葱的叙述方式,拨开1XX状态码的层层面纱。
定义
HTTP状态码(英语:HTTP Status Code)是用以表示网页服务器超文本传输协议响应状态的3位数字代码。这句话要注意解读了。我先来考考大家读懂了没。请回答:

这里我先不回答,无法决定的朋友再多读几遍定义。
状态码1XX表示服务端已经收到了请求,但是还需要进一步处理。
白居易有一首《问刘十九》:
绿蚁新醅酒,红泥小火炉。
晚来天欲雪,能饮一杯无?
这就和服务端可能会返回1XX的场景很像。客户端发起一个请求:我这里有美酒和暖炉,天要下雪了,来喝一杯?服务端收到之后返回1XX。代表接受邀请。这时候客户端就可以真正摆上一桌酒菜迎接客人了。如果服务端返回4XX,客户端也不用消耗资源杀鸡做菜了。
1xx状态码是 HTTP/1.1 版本新定义的,用来表示请求被正常接收,会进行进一步处理。这些状态码相对较新,并且 HTTP/1.0 版本无法识别,所以原则上不应该向HTTP/1.0版本的客户端发送任何1xx状态码。
100 Continue

该状态码说明服务器收到了请求的初始部分,并请客户端继续发送。在服务器发送了 100 Continue 状态码之后,如果收到客户端的请求,则必须进行响应。
此状态码实际上是对以下场景的优化:客户端有一个大文件需要上传和保存,但客户端不知道服务器是否愿意接受该文件,因此它想在消耗网络资源进行传输之前询问服务器自己的意愿。实际操作向客户端发送一条特殊的请求消息,该消息的头部应该包含
[En]
This status code is actually an optimization of the following scenario: the client has a large file that needs to be uploaded and saved, but the client does not know whether the server is willing to accept the file, so it wants to ask the server about its wishes before consuming network resources for transmission. The actual operation sends a special request message for the client, and the header of the message should contain
Expect: 100-continue
此时,如果服务器愿意接受,就会返回 100 Continue 状态码,反之则返回 417 Expectation Failed 状态码。对于客户端而言,如果客户端没有发送实际请求的打算,则不应该发送包含 100 Continue Expect 的报文,因为这样会让服务器误以为客户端将要发送一个请求。大家可以基于对100的理解再回想一下《问刘十九》。
之前提到过,并不是所有的HTTP应用都支持 100 Continue 这个状态码(例如HTTP/1.0及之前的版本的代理或服务器)所以客户端不应该在发送 100 Continue Expect 后一直等待服务器的响应,在一定时间后,客户端应当直接发送计划发送的内容。
而对于服务器而言,也不应当把 100 Continue 当作一个严格的判断方法。服务器有可能在发送回应之前就收到了客户端发来的主体报文。此时服务器就不需要再发送 100 Continue 作为回应了。但仍然需要在接受完成后返回适当的状态码。理论上,当服务器收到一个 100 Continue Expect 请求时,应当进行响应。但服务器永远也不应向没有发送 100 Continue Expect 请求的客户端发送100 Continue 状态码作为回应。这里提到的应当进行响应是指:假设服务器不打算接收客户端将要发送的主体报文,也应当做适当的响应(例如发送 417 Expectation Failed)而不是单纯的关闭连接,这样会对客户端在网络层面上产生影响。
作为代理的HTTP应用在收到带有 100 Continue Expect 的请求时,需要进行额外的判断。假设代理服务器明确知道报文下游的HTTP版本是兼容 HTTP/1.1 的,或者代理服务器不知道报文下游的版本,它都应当转发这条 100 Continue Expect 请求。但是如果代理服务器明确知道报文下游的应用无法处理 100 Continue Expect 的话,则应当直接向客户端返回 417 Expectation Failed 作为响应。而这也并非唯一的解决办法,另一种可行的办法是直接向客户端返回 100 Continue ,然后向下游传递删除了 100 Continue Expect 的报文。
另外,如果代理服务器决定为 HTTP/1.0 及之前的版本服务的话,那么当它收到来自服务器的 100 Continue 响应报文时,则不应当向客户端转发这条响应,因为客户端很可能不知道如何处理该报文。
101 Switching Protocols
HTTP 101 Switching Protocol(协议切换)状态码表示服务器应客户端升级协议的请求对协议进行切换。
此机制始终由客户端发起,并且服务器可能接受或拒绝切换到新协议。客户端可使用常用的协议(如HTTP / 1.1)发起请求,请求说明需要切换到HTTP / 2或甚至到WebSocket。
我们来看一个实际的例子:

为了实现WebSocket通信,首先需要客户端发起一次普通HTTP请求。也就是说,WebSocket的建立是依赖HTTP的。

其中HTTP头部字段Upgrade: websocket和Connection: Upgrade非常重要,告诉服务器通信协议将发生改变,转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后,应返回状态码为101 Switching Protocols的响应:

其中字段Sec-WebSocket-Accept是由服务器对前面客户端发送的Sec-WebSocket-Key进行确认和加密后的结果,相当于一次验证,以帮助客户端确信对方是真实可用的WebSocket服务器。
验证通过后,这个握手响应就确立了WebSocket连接,此后,服务器端就可以主动发信息给客户端了。此时的状态比较像服务器端和客户端接通了电话,无论是谁有什么信息想告诉对方,开口就好了。
一旦建立了WebSocket连接,此后的通信就不再使用HTTP了,改为使用WebSocket独立的数据帧。

102 Processing
102 Processing是由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
Web服务器可能需要相当长的时间来处理复杂的请求。当客户端的浏览器发送包含多个涉及复杂需求的子请求的WebDAV请求时,服务器需要一些时间来处理并发送此代码”102–Processing”。此代码旨在通过通知客户端服务器收到请求并对其进行处理来避免客户端出现超时错误。
总结
HTTP状态码的设计规则是:前面一位是分类。2XX表示服务端已经收到了请求,并且已经分析处理完;3XX表示服务端已经收到了请求,但是还需要其他资源或者服务处理;4XX表示服务端已经收到了请求,但是无法理解,说明客户端请求姿势不正确;5XX表示服务端已经收到了请求,但是由于服务端自身问题无法正确响应。
这种编码方法在很多地方都在用。
比如身份证号规则
前1、2位数字表示:所在省(直辖市、自治区)的代码;第3、4位数字表示:所在地级市(自治州)的代码;第5、6位数字表示:所在区(县、自治县、县级市)的代码;第7—14位数字表示:出生年、月、日;第15、16位数字表示:所在地的派出所的代码;第17位数字表示性别:奇数表示男性,偶数表示女性;第18位数字是校检码。
比如行别代码
支付系统为每一类参与者分配一个识别号,该识别号由三个数字组成,第一个是银行类型,其余两个是识别号。
[En]
The payment system assigns an identification number to each category of participants, which consists of three digits, the first is the bank type, and the remaining two are identification numbers.
支付系统的参与者行号规则与身份证规则非常相似,只是每一位都有特殊的含义,最后一位也是验证码。无论如何,我写下了参加人数的规则。默默地写下八遍不是一件容易的事。
[En]
The participant line number rule of the payment system is very similar to the ID card rule, except that each bit has a special meaning, and the last digit is also a CAPTCHA. Anyway, I wrote down the rules of the number of participants. It’s not easy to write it down silently for eight times.
Original: https://www.cnblogs.com/xiexj/p/15854376.html
Author: 编程一生
Title: HTTP状态码1XX深入理解
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/163767/
转载文章受原作者版权保护。转载请注明原作者出处!