SpringBoot之SpringMVC自动配置相关原理分析

346 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

一、自动配置的分析

Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合。

springboot给我们内置了自动配置类:WebMvcAutoConfiguration.java,自动配置在Spring的默认值之上添加了以下功能:

包含ContentNegotiatingViewResolverBeanNameViewResolver

  • ViewResolver 都是SpringMVC内置的视图解析器
    • ContentNegotiatingViewResolver:他并不会解析视图、而是委派给其他视图解析器进行解析
      所有视图解析器,都会根据返回的视图名称进行解析视图 resolveViewName

1-1、ContentNegotiatingViewResolver是如何进行视图委派解析的

image.png

那最终ContentNegotiatingViewResolver通过resolveViewName是怎么委派给其他视图解析器进行解析的呢?我们接着往下看。

进入getCandidateViews

image.png

进入这个方法,他首先获得了所有的视图,然后进行迭代匹配

image.png

其中viewResolvers是如何获取的呢?我们往上面看,如下图,可以看到这个initServletContext方法,中进行了viewResolvers的初始化操作,这个方法在服务启动的时候就会进行调用。然后从spring容器中拿到匹配ViewResolver的所有Bean

image.png

通过以上的了解,那么我们就可以自己也配置一个ViewResolver的实现类,并注册为BEAN,这样spring在初始化的时候,我们自己写的继承与ViewResolver的类也就会被加载的viewResolver中了,这样就可以帮我们进行委派解析了。

需要注意的是使用viewResolver需要在Controller中返回一个视图才可以使用到这个委派的机制,之前用于做测试的项目,是无法加载到视图解析器的,我们可以打断点试一下。

访问之前通过ID获得用户的接口地址:http://localhost:8080/user/1 这样正常访问时不会进入断点的,因为我们Controller返回的是Result,非视图。然后我们再次访问地址:http://localhost:8080/user/3fdsafg 的时候,因为参数不是Integer,这个时候因为类型错误就会报错,导致跳转到错误页面,这样就经过视图解析器了,如下:

image.png

可以看到已经进入断点,并且viewName为error,即错误页面。

1-1-1、创建自己的视图解析器

为了方便,就直接在application中创建一个Bean,并且使用InternalResourceViewResolver视图解析器 image.png

然后访问http://localhost:8080/user/3fdsafg 看下断点

image.png 可以看到spring容器已经将我们刚刚配置的视图解析器放入到viewResolver中。

由以上代码可以得出结论,它是从Spring IOC容器获得ViewResolver类型Bean,那么我们可以自己定制一个ViewResolver,ContentNegotiatingViewResolver也会帮我们委派解析

1-1-2、ContentNegotiatingViewResolver视图解析器的选择

ContentNegotiatingViewResolver中的viewResolvers属性是一个List集合,其中可以指定多个视图解析器。除此之外ContentNegotiatingViewResolver还会扫描Spring 容器中所有的bean,如果发现了ViewResolver,那么该ViewResolver会供ContentNegotiatingViewResolver利用。

那么ContentNegotiatingViewResolver是如何“智能”地选择视图解析器呢? 有三种方式(优先级从高到底):

  根据请求后缀,比如请求后缀是.xsl,那么它就会判断contentType是application/vnd.ms-excel…以此类推。

  根据请求参数,比如请求参数xxxx/?format=json它就会判断contentType是application/json

  根据Accept请求头,例如请求头包含text/html,他就会判断请求的contextType是text/html

1-2、BeanNameViewResolver

会根据handler方法返回的视图名称, 去ioc容器中到到名字叫这个视图的一个Bean,并且这个bean要实现了VIew接口,下面来创建一个。

1-2-1、创建Controller

首先创建一个控制器,然后跳转到jony视图 image.png

1-2-2、创建视图(jony)

上面控制器跳转到了jony视图,因此我们下面创建一个jony的视图,需要注意的是需要实现View接口。

image.png

1-2-3、测试访问如下

image.png

可以看到如上创建的视图继承了View,那么我们该如何判断继承那个视图呢、比如我们要下载Excel,该继承那个视图呢,可以看下官网

1-2-4、View的选择

首先选择spring framework

image.png

然后查看文档

image.png

进入Web Servlet

image.png

选择PDF and Excel,可以看到官网的说明,excel使用AbstractXlsView视图抽象类

image.png

如下图,我们继承了AbstractXlsView,然后可以重写buildExcelDocument方法,这个方法中有我们熟知的workbook,这样就可以操作excel了。

image.png

二、springboot静态资源的支持

以前要访问jpg\css、js 等 这些静态资源文件, 需要在web.xml配置 ,在springboot不需要配置,只需要放在约定文件夹static中就可以(约定大于配置)

首先在resource/static下创建img文件夹,然后将2.jpg图片复制进去

1653694851(1).png

浏览器直接访问:http://localhost:8080/img/2.jpg

image.png 已经成功访问,不像springmvc的时候还需要对静态资源进行配置,springboot就方便了很多,开箱即用,那springboot是如何实现的呢?接下来,一起探究一下

2-1、静态配置类是如何支持静态资源的

在springboot的自动配置类WebMvcAutoConfiguration中,有一个针对resource处理的方法:addResourceHandlers

image.png 在上图中,可以看到有一个叫webjars的,它是做什么的呢?

2-1-1、WebJars的探究

WebJars: 就是将静态资源放在jar包中进行访问

image.png

往下拉就可以看到webjars给我们提供的相关jar

image.png

然后选择maven的方式,复制对应的maven依赖复制到pom.xml即可

image.png

比如我们想将jquery导入到项目中,在搜索框搜索jquery,然后复制对应的maven依赖即可。

image.png

pom.xml依赖配置,

<!--juqery 的webjars-->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.6.0</version>
</dependency>

下载之后,如下图

image.png

当访问/webjars/** 时 就会去classpath:/META-INF/resources/webjars/ 对应进行映射\

当访问http://localhost:8080/webjars/juqery/3.6.0/jquery.js 对应映射到 /META-INF/resources/webjars/juqery/3.6.0/jquery.js

访问如下:

image.png

2-1-2、静态资源路径的读取

可以看下如下代码,去查看getStaticLocations() image.png

进入getStaticLocations方法

image.png 如上图就可以看到最终读取的是classpath下的地址文件:

{ "classpath:/META-INF/resources/",
      "classpath:/resources/", "classpath:/static/", "classpath:/public/" }

通过以上信息,我们就可以得知,我们可以把文件放在如上的文件夹中,

image.png

在resource中创建public文件夹,然后复制一个2.jpg到public中,然后重新编译一下,就可以看到target中就有public了,然后浏览器访问如下:

image.png

2-2、配置欢迎页

在static下创建index.html image.png

浏览器访问:http://localhost:8080

image.png

这在springboot中是怎么处理的呢?我们再重源码入手查看

image.png 如上图首先加载getWelcomePage,然后同样加载静态资源,逐个目录进行查找index.html

2-3、静态资源路径的设置

如下可以看到当前静态资源文件配置的为(根据springboot的版本,配置略有不同):@ConfigurationProperties("spring.web") image.png

假如我们不想让resource/public生效,只使用static,就可以在资源文件中配置:spring.mvc.static-path-pattern=classpath:/static 这样springboot就只会去static中查找静态文件了,如果有多个可以用“,”分割开。

三、springboot的转换器

3-1、日志转换器

之前在springmvc的文章中有提到过类转换器,可以把日期格式进行转换,然后用springboot再看一下。

首先还是到自动配置类WebMvcAutoConfiguration.java中找到mvcConversionService方法,如下:

@Bean
@Override
public FormattingConversionService mvcConversionService() {
   Format format = this.mvcProperties.getFormat();
   WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
         .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
   addFormatters(conversionService);
   return conversionService;
}

可以看到这个转换器中对日期进行了处理,我们进入DateTimeFormatters这个类

image.png

然后看一下这个类中的DateTimeFormatters

image.png 通过上面代码可以看到如果pattern没有传入值,则时间默认格式为yyyy-MM-dd,如果传入了则以传入的为准,

然后回到自动配置类中,继续看一下这个方法,是如何获得配置信息的,如下

image.png

如下代码,我们可以得知,配置时间格式,可以通过spring.mvc.format.date进行配置。

image.png

3-2、数据转换器HttpMessageConverters

我们在使用控制器的时候,从控制器向前端发送JavaBean,前端接收到的为Json数据,当前端向后端提交Json数据的时候,后端我们就可以使用JavaBean来进行接收,这都归功于HttpMessageConverters,大概工作顺序为:

image.png

3-3、自动注册MessageCodesResolver

  • 修改4xx 错误下 格式换转换出错 类型转换出错的 错误代码:
  • 以前的格式
  • errorCode + "." + object name + "." + field
  • typeMismatch.user.birthday
  • 可以通过

spring.mvc.message-codes-resolver-format=postfix_error_code

将格式修改为:object name + "." + field + "." + errorCode

四、springboot中html的支持

在springboot中可以直接返回html的视图

4-1、在Controller中添加html的跳转

/**
 * 跳转到html
 * @return
 */
@RequestMapping("/html")
public String toHtml(){
    return "hello";
}

4-2、在application.properties中添加视图配置信息

spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.html

4-3、添加hello.html页面

image.png

4-4、在自动配置类WebMvcAutoConfigruation中查看

image.png 如上代码可以看到通过读取前缀和后缀的配置信息。