10.SpringBootWEB的自动配置
十、SpringBootWeb的自动配置原理
1、自动配置原理
10.1.1、WEB自动配置原理
(1)、自动配置的依赖传递
-
首先引入了web启动器
org.springframework.boot spring-boot-starter-web
-
web启动器引入了spring-boot-starter启动器
org.springframework.boot spring-boot-starter 3.4.3 compile
-
spring-boot-starter会传递引入一个spring-boot-autoconfigure包
org.springframework.boot spring-boot-autoconfigure 3.4.3 compile
-
在spring-boot-autoconfigure包中的.imports文件中罗列的需要导入的自动配置类
(2)、WEB自动配置原理
测试模块springboot-10-001
-
从入口程序开始
入口程序被@SpringBootApplication注解标注
@SpringBootApplication public class Springboot10001Application { public static void main(String[] args) { SpringApplication.run(Springboot10001Application.class, args); } }
-
@SpringBootApplication注解被@EnableAutoConfiguration注解标注,表示启用自动配置
-
@EnableAutoConfiguration注解被@Import({AutoConfigurationImportSelector.class})注解标注
因此AutoConfigurationImportSelector决定哪些自动配置类是需要导入的
-
AutoConfigurationImportSelector底层实现步骤具体如下
跟踪selectImports()方法源码
跟踪getAutoConfigurationEntry()方法
跟踪getCandidateConfigurations()方法
继续跟踪load()方法
最终找的文件是:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
注意:任何jar包,包括第三方的依赖,自动配置类所在的路径以及文件名都是完全相同的,都是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
例如mybatis的自动配置类的列表文件也是这样
(3)、逆推web配置的prefix
在自动配置列表中找到web自动配置相关的类
以下就是web自动配置类列表
通过web自动配置类的源码可以逆推web配置的prefix
-
WebMvcAutoConfiguration
-
MultipartAutoConfiguration
-
HttpEncodingAutoConfiguration
-
ErrorMvcAutoConfiguration
-
ServletWebServerFactoryAutoConfiguration
-
DispatcherServletAutoConfiguration
-
EmbeddedWebServerFactoryCustomizerAutoConfiguration
-
RestTemplateAutoConfiguration
通过查看源码得知web开发时在application.properties配置文件中可以配置的前缀有四个
# SpringMVC相关配置 spring.mvc. # web开发通用配置 spring.web. # 文件上传配置 spring.servlet.multipart. # 服务器配置 server.
(4)、WEB自动配置功能
查看官方文档https://docs.spring.io/spring-boot/reference/web/servlet.html
翻译如下
Spring Boot 为 Spring MVC 提供了自动配置,这在大多数应用程序中都能很好地工作
除了已经实现了 Spring MVC 的默认功能外,自动配置还提供了以下特性:
- 包括 ContentNegotiatingViewResolver 和 BeanNameViewResolver 的 Bean
- ContentNegotiatingViewResolver 自动根据HTTP请求头中Accept字段来选择合适的视图技术渲染响应
- BeanNameViewResolver 的作用是根据视图名称找到视图View对象
- 支持提供静态资源(包括对 WebJars的支持)
- 静态资源路径默认已经配置好了,默认会去static目录下找
- 自动注册 Converter、GenericConverter 和 Formatter 的 Bean
- Converter:转换器,做类型转换的(例如表单提交了用户数据,将表单数据转换成User对象)
- Formatter:格式化器,做数据格式化的(例如将Java中的日期类型对象格式化为特定格式的日期字符串或者将用户提交的日期字符串转换为Java中的日期对象)
- 支持 HttpMessageConverters
- 内置了很多的HTTP消息转换器(例如:MappingJackson2HttpMessageConverter可以将json转换成java对象、也可以将java对象转换为json字符串)
- 自动注册 MessageCodesResolver
- SpringBoot会自动注册一个默认的消息代码解析器
- 帮助你在表单验证出错时生成一些特殊的代码。这些代码让你能够更精确地定位问题并提供更友好的错误提示
- 静态 index.html 文件支持
- Spring Boot 会自动处理位于项目静态资源目录下的 index.html 文件,使其成为应用程序的默认主页
- 自动使用 ConfigurableWebBindingInitializer Bean
- 用它来指定默认使用哪个转换器、默认使用哪个格式化器,在这个类当中都已经配好了
如果不想使用自动配置并希望完全控制 Spring MVC,可以添加自己的带有@EnableWebMvc注解的@Configuration`
如果希望保留这些 Spring Boot MVC 定制化设置并进行更多的 MVC 定制化(如拦截器、格式化程序、视图控制器等其他功能),可以添加自己的类型为 WebMvcConfigurer 的 @Configuration 类,但不能使用@EnableWebMvc注解
10.1.2、WebMvc自动配置原理
通过源码分析的方式查看WebMvc的自动配置原理
(1)、自动配置生效条件
WebMvc自动配置是否生效的条件
@AutoConfiguration( after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class} ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @ImportRuntimeHints({WebResourcesRuntimeHints.class}) public class WebMvcAutoConfiguration {}
- @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
- WebMvcAutoConfiguration自动配置类加载顺序在以上自动配置类加载后加载
- @ConditionalOnWebApplication(type = Type.SERVLET)
- WebMvcAutoConfiguration自动配置类只在servlet环境中生效
- @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
- 类路径中必须存在Servlet.class``DispatcherServlet.class``WebMvcConfigurer.class,WebMvcAutoConfiguration自动配置类才会生效
- @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- 类路径中不存在WebMvcConfigurationSupport.class时WebMvcAutoConfiguration自动配置类才会生效
- 注意:当使用@EnableWebMvc注解后类路径中就会注册一个WebMvcConfigurationSupport这样的bean
- @AutoConfigureOrder(-2147483638)不重要
- 指定WebMvcAutoConfiguration自动配置类的加载顺序
- @ImportRuntimeHints(WebResourcesRuntimeHints.class) 不重要
- 运行时引入WebResourcesRuntimeHints这个类,这个类的作用是给JVM或者其他组件提示信息的,提示一下系统应该如何处理类和资源
总结来说WebMvcAutoConfiguration类将在以下条件下生效
- 应用程序是一个Servlet类型的Web应用;
- 环境中有Servlet、DispatcherServlet和WebMvcConfigurer类;
- 容器中没有WebMvcConfigurationSupport的bean
如果这些条件都满足的话那么这个自动配置类就会被激活并进行相应的自动配置工作
来看一下WebMvcAutoConfiguration的生效条件
上图红框内表示要求Spring容器中缺失WebMvcConfigurationSupport这个Bean,WebMvcAutoConfiguration才会生效
但是看一下EnableWebMvcConfiguration的继承结构
很明显EnableWebMvcConfiguration就是一个WebMvcConfigurationSupport这样的Bean
既然容器中存在WebMvcConfigurationSupport这样的Bean,WebMvcAutoConfiguration为什么还会生效呢
原因是因为:EnableWebMvcConfiguration是WebMvcAutoConfiguration类的内部类。在WebMvcAutoConfiguration进行加载的时候,EnableWebMvcConfiguration这个内部类还没有加载。因此这个时候在容器中还不存在WebMvcConfigurationSupport的Bean,所以WebMvcAutoConfiguration仍然会生效
以上所说的WebMvcAutoConfiguration类中的内部类EnableWebMvcConfiguration是用来启用Web MVC默认配置的
注意区分:WebMvcAutoConfiguration的两个内部类
- WebMvcAutoConfigurationAdapter作用是用来:修改配置的
- EnableWebMvcConfiguration作用是用来:启用配置的
(2)、自动引入两个Filter
WebMvc自动配置生效后引入了两个Filter Bean
HiddenHttpMethodFilter
这个过滤器是专门处理Rest请求的:GET POST PUT DELETE请求
FormContentFilter
引入了FormContentFilter Bean
OrderedFormContentFilter 是 Spring Boot 中用于处理 HTTP 请求的一个过滤器,特别是针对 PUT 和 DELETE 请求。这个过滤器的主要作用是在处理 PUT 和 DELETE 请求时,确保如果请求体中有表单格式的数据,这些数据会被正确解析并可用
(3)、WebMvcConfigurer接口的实现类
WebMvc自动配置生效后引入了WebMvcConfigurer接口的实现类
在SpringBoot框架的WebMvcAutoConfiguration类中提供了一个内部类:WebMvcAutoConfigurationAdapter
SpringBoot在这个类WebMvcAutoConfigurationAdapter中进行了一系列的Spring MVC相关配置
开发中要对Spring MVC的相关配置进行修改,可以编写一个类继承WebMvcAutoConfigurationAdatper,然后重写对应的方法即可
因此通过对WebMvcAutoConfigurationAdapter类中的方法进行重写来修改Web MVC的默认配置
WebMvcConfigurer接口
这个接口不是SpringBoot框架提供的,是Spring MVC提供的,在Spring框架4.3版本中引入的
这个接口的作用主要是允许开发者通过实现这个接口来定制Spring MVC的行为
在这个接口中提供了很多方法,需要改变Spring MVC的哪个行为则重写对应的方法即可
下面是这个接口中所有的方法以及每个方法对应的Spring MVC行为的解释
public interface WebMvcConfigurer { // 用于定制 Spring MVC 如何匹配请求路径到控制器 default void configurePathMatch(PathMatchConfigurer configurer) {} // 用于定制 Spring MVC 的内容协商策略,以确定如何根据请求的内容类型来选择合适的处理方法或返回数据格式 default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {} // 用于定制 Spring MVC 处理异步请求的方式 default void configureAsyncSupport(AsyncSupportConfigurer configurer) {} // 用于定制是否将某些静态资源请求转发WEB容器默认的Servlet处理 default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {} // 用于定制 Spring MVC 解析视图的方式,以确定如何将控制器返回的视图名称转换为实际的视图资源。 default void configureViewResolvers(ViewResolverRegistry registry) {} // 用于定制 Spring MVC 如何处理 HTTP 请求和响应的数据格式,包括 JSON、XML 等内容类型的转换 default void configureMessageConverters(List> converters) {} // 用于定制 Spring MVC 如何处理控制器方法中抛出的异常,允许你添加额外的异常处理逻辑。 default void extendHandlerExceptionResolvers(List resolvers) {} }
WebMvcConfigurer实现类
WebMvcConfigurer`接口的实现类`WebMvcAutoConfigurationAdapter
WebMvcAutoConfigurationAdapter是Spring Boot框架提供的,实现了Spring MVC中的WebMvcConfigurer接口,对Spring MVC的所有行为进行了默认的配置
查看WebMvcAutoConfigurationAdapter类的源码
可以看到该类上有一个注解@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }),该注解负责启用配置属性。会将配置文件application.properties或application.yml中的配置传递到该类中。因此可以通过application.properties或application.yml配置文件来改变Spring Boot对SpringMVC的默认配置
WebMvcProperties和WebProperties源码如下
@ConfigurationProperties( prefix = "spring.mvc" ) public class WebMvcProperties {} @ConfigurationProperties("spring.web") public class WebProperties {}
通过以上源码得知要改变SpringBoot对SpringMVC的默认配置需要在配置文件中使用以下前缀的配置
- spring.mvc:主要用于配置 Spring MVC 的相关行为(例如路径匹配、视图解析、静态资源处理等)
- spring.web:通常用于配置一些通用的 Web 层设置(如资源处理、安全性配置等)
2、静态资源处理
web站点中的静态资源指的是:js、css、图片等
10.2.1、自动配置的静态资源处理
(1)、源码分析
关于SpringBoot对静态资源处理的默认配置,查看WebMvcAutoConfigurationAdapter源码,核心源码如下
对以上源码进行解释
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 检查 resourceProperties 中的 addMappings 属性是否为 false。如果为 false则表示不启用默认的静态资源映射处理 // 在application.properties配置文件中进行`spring.web.resources.add-mappings=false`配置,可以将其设置为false // 当然如果没有配置的话默认值是true if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } // 配置 WebJars 的静态资源处理。 // this.mvcProperties.getWebjarsPathPattern()的执行结果是:/webjars/** // 也就是说,如果请求路径是 http://localhost:8080/webjars/** ,则自动去类路径下的 /META-INF/resources/webjars/ 目录中找静态资源。 // 如果要改变这个默认的配置,需要在application.properties文件中进行这样的配置:`spring.mvc.webjars-path-pattern=...` addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/"); // 配置普通静态资源处理 // this.mvcProperties.getStaticPathPattern()的执行结果是:/** // this.resourceProperties.getStaticLocations()的执行结果是:{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" } // 也就是说如果请求路径是:http://localhost:8080/**,根据控制器方法优先原则会先去找合适的控制器方法,如果没有合适的控制器方法,静态资源处理才会生效,则自动去类路径下的/META-INF/resources/、/resources/、/static/、/public/ 4个位置找 // 如果要改变这个默认的配置,需要在application.properties中进行如下的两个配置: // 配置URL:spring.mvc.static-path-pattern=... // 配置物理路径:spring.web.resources.static-locations=...,...,...,... addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); }
(2)、WebJars静态资源处理
在SpringBoot中对WebJars的默认访问规则是:当请求路径是/webjars/**则会去classpath:/META-INF/resources/webjars/找
WebJars介绍
WebJars 是一种将常用的前端库(如 jQuery、Bootstrap、Font Awesome 等)打包成 JAR 文件的形式,方便在 Java 应用程序中使用。WebJars 提供了一种标准化的方式来管理前端库,使其更容易集成到 Java 项目中,并且可以利用 Maven 的依赖管理功能
WebJars在SpringBoot中的使用
WebJars官网:https://www.webjars.org/
在官网上可以找到某个webjars的maven依赖,将依赖加入到SpringBoot项目中,例如添加vue的依赖
org.webjars.npm vue 3.5.12
如下图表示加入成功
在jar包列表中也可以看到
在SpringBoot中对WebJars的默认访问规则是当请求路径是/webjars/**则会去classpath:/META-INF/resources/webjars/找
因此要想访问上图的index.js则应该发送这样的请求路径:http://localhost:8080/webjars/vue/3.5.12/index.js
测试结果如下
和IDEA中的文件对比一下,完全一样则表示测试成功
(3)、普通静态资源处理
(1)普通静态资源处理
SpringBoot对普通静态资源处理的规则是
当请求路径是http://localhost:8080/**,根据控制器方法优先原则,会先去找合适的控制器方法,如果没有合适的控制器方法,静态资源处理才会生效,则自动去类路径下的以下4个位置查找:
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/
- classpath:/public/
可以在项目中分别创建以上4个目录
在4个目录当中放入静态资源,例如4张图片
测试是否可以正常访问图片
http://localhost:8080/dog1.jpg
http://localhost:8080/dog2.jpg
http://localhost:8080/dog3.jpg
http://localhost:8080/dog4.jpg
(2)静态资源缓存处理
不管是webjars的静态资源还是普通静态资源,统一都会执行以下这个方法,这个方法最后几行代码就是关于静态资源的缓存处理方式。
静态资源缓存指的是浏览器的缓存行为,浏览器可以将静态资源(js、css、图片、声音、视频)缓存到浏览器中,只要下一次用户访问同样的静态资源直接从缓存中取,不再从服务器中获取,可以降低服务器的压力,提高用户的体验
而这个缓存策略可以在服务器端程序中进行设置,SpringBoot对静态资源缓存的默认策略就是以下这三行代码:
以上三行代码的解释如下
-
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
-
设置缓存的过期时间(如果没有指定单位,默认单位是秒)
-
浏览器会根据响应头中的缓存控制信息决定是否从本地缓存中加载资源,而不是每次都从服务器重新请求。这有助于减少网络流量和提高页面加载速度。
-
假设你配置了静态资源缓存过期时间为 1 小时(3600 秒),那么浏览器在首次请求某个静态资源后,会在接下来的一小时内从本地缓存加载该资源,而不是重新请求服务器。
-
可以通过application.properties的来修改默认的过期时间
例如:spring.web.resources.cache.period=3600或者spring.web.resources.cache.period=1h
-
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
-
设置静态资源的 Cache-Control HTTP 响应头,告诉浏览器如何去缓存这些资源。
-
Cache-ControlHTTP 响应头是HTTP响应协议的一部分内容
-
如下图响应协议的响应头信息中即可看到Cache-Control的字样
常见的 Cache-Control 指令包括
max-age=:表示响应在多少秒内有效
public:表示响应可以被任何缓存机制(如代理服务器)缓存
private:表示响应只能被用户的浏览器缓存
no-cache:表示在使用缓存的资源之前必须重新发送一次请求进行验证
no-store:表示不缓存任何响应的资源
- 例如:max-age=3600, public:表示响应在 3600 秒内有效,并且可以被任何缓存机制缓存。 - 可以通过`spring.web.resources.cache.cachecontrol.max-age=3600`以及`spring.web.resources.cache.cachecontrol.cache-public=true`进行重新配置
-
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
-
设置静态资源在响应时是否在响应头中添加资源的最后一次修改时间
SpringBoot默认配置的是在响应头中添加响应资源的最后一次修改时间
-
浏览器发送请求时会将缓存中的资源的最后修改时间和服务器端资源的最后一次修改时间进行比对,如果没有变化则仍然从缓存中获取
-
可以通过spring.web.resources.cache.use-last-modified=false来进行重新配置
(3)静态资源缓存配置
根据之前源码分析得知静态资源缓存相关的配置应该使用spring.web.resources.cache
跟踪源码
(4)静态资源缓存测试
在application.properties文件中对缓存进行如下的配置
# 静态资源缓存设置 # 1. 缓存有效期 spring.web.resources.cache.period=100 # 2. 缓存控制(cachecontrol配置的话,period会失效) spring.web.resources.cache.cachecontrol.max-age=20 # 3. 是否使用缓存的最后修改时间(默认是:使用) spring.web.resources.cache.use-last-modified=true # 4. 是否开启静态资源默认处理方式(默认是:开启) spring.web.resources.add-mappings=true
注意:cachecontrol.max-age配置的话,period会被覆盖。
启动服务器测试:看看是否在20秒内走缓存,20秒之后是不是就不走缓存了!!!
第一次访问:请求服务器
第二次访问:20秒内开启一个新的浏览器窗口,再次访问,发现走了缓存
第三次访问:20秒后开启一个新的浏览器窗口,再次访问,发现重新请求服务器
显示304是因为这个配置:spring.web.resources.cache.use-last-modified=true
(4)、web应用的欢迎页面
(1)欢迎页测试
只要在静态资源路径下提供index.html则被当做欢迎页面
静态资源路径指的是之前的4个路径
{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }
测试一下在classpath:/static/目录下新建index.html页面
主页
springboot项目的欢迎页面(classpath:/static/)
测试结果如下http://localhost:8080/
(2)欢迎页顺序
同时在4个静态资源路径下都提供index.html
测试结果如下
(3)源码分析
在Resources类中定义了四个路径
在WebMvcAutoConfiguration类中有一个内部类EnableWebMvcConfiguration,这个类中有这样一段代码:
跟踪getIndexHtmlResource()方法
继续跟踪getIndexHtmlResource(String location)方法
继续向下查找
通过以上源码追踪得出结论:只要请求路径是/**的会依次去{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }这四个位置找index.html页面作为欢迎页
(4)favorite icon
favicon(也称为“收藏夹图标”或“网站图标”)是大多数现代网页浏览器的默认行为之一
当用户访问一个网站时浏览器通常会尝试从该网站的根目录下载名为 favicon.ico 的文件,并将其用作标签页的图标
如果网站没有提供 favicon.ico 文件,浏览器可能会显示一个默认图标,或者根本不显示任何图标
为了确保良好的用户体验,网站开发者通常会在网站的根目录下放置一个 favicon.ico 文件
Spring Boot 会在配置的静态内容位置检查是否存在 favicon.ico文件。如果存在这样的文件,它将自动作为应用程序的 favicon 使用。
因此只需将favicon.ico文件放到静态资源路径下即可
web站点没有提供favicon.ico时:
在https://www.iconfont.cn/ (阿里巴巴提供的图标库)上随便找一个图标,然后将图片名字命名为favicon.ico,然后将其放到SpringBoot项目的静态资源路径下:
ctrl + F5强行刷新一下,避免影响测试效果
10.2.2、手动配置
如果对SpringBoot默认的静态资源处理方式不满意,可以通过两种方式来改变这些默认的配置:
-
第一种:配置文件方式
-
通过修改application.properties或application.yml
-
添加spring.mvc和spring.web相关的配置
-
第二种:编写代码方式
SpringMVC框架为我们提供了WebMvcConfigurer接口,需要改变默认的行为,可以编写一个类实现WebMvcConfigurer接口,并对应重写接口中的方法即可改变默认的配置行为
(1)、配置文件方式
要修改访问静态资源URL的前缀,这样配置:
# Spring MVC的相关配置 # 1. 设置webjars静态资源的请求路径的前缀 spring.mvc.webjars-path-pattern=/wjs/** # 2. 设置普通静态资源的请求路径的前缀 spring.mvc.static-path-pattern=/static/**
要修改静态资源的存放位置,这样配置:
spring.web.resources.static-locations=classpath:/static1/,classpath:/static2/
进行以上配置之后:
- 访问webjars的请求路径应该是这样的:http://localhost:8080/wjs/…
- 访问普通静态资源的请求路径应该是这样的:http://localhost:8080/static/…
- 普通静态资源的存放位置也应该放到classpath:/static1/,classpath:/static2/下面,其他位置无效
访问webjars测试结果如下
访问普通静态资源测试结果如下:
如果访问dog2.jpg,就无法访问了:
但是存储在classpath:/META-INF/resources/目录下的dog1.jpg仍然是可以访问的
因此存储在classpath:/META-INF/resources/位置的静态资源会被默认加载,不受手动配置的影响
(2)、编写代码方式
想要定制Spring MVC的行为,也可以编写类实现Spring MVC框架提供的一个接口WebMvcConfigurer,想定制哪个行为就重写哪个方法即可。
编写的类只要纳入IoC容器的管理
因此有以下两种实现方式
- 第一种:编写类实现WebMvcConfigurer接口,重写对应的方法。
- 第二种:以组件的形式存在:编写一个方法,用@Bean注解标注。
第一种方式
编写配置类,对于web开发来说,配置类一般起名为:WebConfig。配置类一般存放到config包下,因此在SpringBoot主入口程序同级目录下新建config包,在config包下新建WebConfig类:
package com.longdidi.config; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; // 这里的这种配置:在springboot默认的自动配置基础之上进行扩展配置。也就是说springboot之前的配置仍然生效。 //@Configuration public class WebConfig implements WebMvcConfigurer { // 静态资源处理,需要重写的方法是:addResourceHandlers @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径 registry.addResourceHandler("/abc/**") // 配置路径访问模式 .addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径 } } /*// 添加这个注解之后,表示不在使用springboot的提供的默认的自动配置。 @EnableWebMvc // 以下配置完全使用自己的,不在基于springboot的配置进行扩展。 @Configuration public class WebConfig implements WebMvcConfigurer { // 静态资源处理,需要重写的方法是:addResourceHandlers @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径 registry.addResourceHandler("/abc/**") // 配置路径访问模式 .addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径 } }*/
注意:将application.properties文件中之前的所有配置全部注释掉,让其恢复到最原始的默认配置
启动服务器进行测试
访问http://localhost:8080/abc/x.jpg
通过测试可以看到配置是生效的
测试默认的配置是否还生效http://localhost:8080/dog1.jpg
可以看到Spring Boot对Spring MVC的默认自动配置是生效的
因此以上的方式只是在Spring MVC默认行为之外扩展行为
如果不想再继续使用SpringBoot提供的默认行为可以使用@EnableWebMvc进行标注
例如:
package com.longdidi.config; /*// 这里的这种配置:在springboot默认的自动配置基础之上进行扩展配置(也就是说springboot之前的配置仍然生效) @Configuration public class WebConfig implements WebMvcConfigurer { // 静态资源处理,需要重写的方法是:addResourceHandlers @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径 registry.addResourceHandler("/abc/**") // 配置路径访问模式 .addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径 } }*/ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; // 添加这个注解之后表示不在使用springboot的提供的默认的自动配置 @EnableWebMvc // 以下配置完全使用自己的,不在基于springboot的配置进行扩展 @Configuration public class WebConfig implements WebMvcConfigurer { // 静态资源处理,需要重写的方法是:addResourceHandlers @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径 registry.addResourceHandler("/abc/**") // 配置路径访问模式 .addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径 } }
测试默认配置结果http://localhost:8080/dog1.jpg
测试手动配置结果http://localhost:8080/abc/x.jpg
第二种方式
采用@Bean注解提供一个WebMvcConfigurer组件,代码如下:
package com.longdidi.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; //@EnableWebMvc @Configuration public class WebConfig2 { @Bean public WebMvcConfigurer aaa(){ return new WebMvcConfigurer() { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/xyz/**") .addResourceLocations("classpath:/static1/", "classpath:/static2/"); } }; } }
测试默认配置http://localhost:8080/dog1.jpg
测试手动配置http://localhost:8080/xyz/x.jpg
通过了测试并且以上代码也是在原有配置基础上进行扩展
如果要弃用默认配置,仍然使用@EnableWebMvc注解进行标注
3、web请求的路径匹配
在Spring Boot3中,对web请求的路径匹配提供了两种规则:
- 第一种:AntPathMatcher(Ant风格)【较旧】
- 第二种:PathPatternParser(从Spring5.3中引入的。在SpringBoot2.4中引入的)【较新:效率高】
- 效率比Ant高,一般新项目中使用PathPatternParser
SpringBoot3中默认使用的是PathPatternParser,不需要任何配置。如果要使用AntPathMatcher,就需要进行如下的配置:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
10.3.1、匹配方式用法
(1)、AntPathMatcher
Ant风格的路径匹配规则回顾:
-
匹配任意长度的任意字符序列(不包括路径分隔符)
示例:/foo/*.html 匹配 /foo/bar.html 和 /foo/baz.html
-
**
匹配任意数量的目录层级
示例:/foo/** 匹配 /foo/bar、/foo/bar/baz 和 /foo/bar/baz/qux
-
?**
匹配任意单个字符**
示例:/foo?bar 匹配 /foobar 和 /fooxbar
-
[]**
匹配指定范围内的单个字符
示例:/foo[a-z]bar 匹配 /fooabar、/foobbar 等
-
{}**
**路径变量,用于提取路径的一部分作为参数
示例:/users/{userId} 匹配 /users/123,提取 userId=123
如果在SpringBoot3中启用Ant风格,记得配置:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
【示例】在测试模块springboot-10-002中测试
application.properties spring.mvc.pathmatch.matching-strategy=ant_path_matcher AntController.java package com.longdidi.controller; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class AntController { @GetMapping("/{path:[a-z]+}/a?/**/*.do") public String path(HttpServletRequest request, @PathVariable String path) { return request.getRequestURI() + "," + path; } }
测试http://localhost:8080/xyz/ab/111/222/test.do
(2)、PathPatternParser
项目中不做配置,或者按照以下方式配置,都是PathPatternParser:
spring.mvc.pathmatch.matching-strategy=path_pattern_parser
PathPatternParser风格是兼容Ant风格的
只有一个地方PathPatternParser不支持,Ant支持
- 在Ant风格中,**可以出现在任意位置
- 在PathPatternParser中只允许**出现在路径的末尾
因此路径当中如果出现**,那么必须使用Ant风格
除此之外PathPatternParser与AntPathMatcher用法相同
可以测试一下将配置文件中的Ant风格注释掉,采用PathPatternParser风格,然后控制器代码如下:
package com.longdidi.controller; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class PatternController { @GetMapping("/{path:[a-z]+}/a?/**/*.do") // @GetMapping("/{path:[a-z]+}/a?/**") public String testPath(HttpServletRequest request, @PathVariable String path) { String requestURI = request.getRequestURI(); return requestURI + "path = " + path; } }
启动服务器报错:
提示如果在路径当中出现了**,需要将路径匹配规则替换为Ant风格
因此路径当中如果出现**,那么必须使用Ant风格
除此之外PathPatternParser与AntPathMatcher用法相同
再来测试一下**放到末尾
package com.longdidi.controller; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class PatternController { //@GetMapping("/{path:[a-z]+}/a?/**/*.do") @GetMapping("/{path:[a-z]+}/a?/**") public String testPath(HttpServletRequest request, @PathVariable String path) { String requestURI = request.getRequestURI(); return requestURI + "path = " + path; } }
启动服务器测试http://localhost:8080/xyz/ab/111/222/test.do
10.3.2、路径匹配相关源码
底层选择路径匹配规则的源码是
4、内容协商
内容协商机制是Spring MVC框架提供的,接下来主要是学习在SpringBoot中是如何支持SpringMVC内容协商机制的。
10.4.1、内容协商简介
内容协商机制是指服务器根据客户端的请求来决定返回资源的最佳表示形式
白话文描述:客户端要什么格式的数据,后端就应该返回什么格式的数据
- 客户端要JSON就响应JSON
- 客户端要XML就响应XML
- 客户端要YAML就响应YAML
可能会有疑问:客户端接收数据时统一采用一种格式(例如JSON)不就行了吗?
但在实际的开发中不是这样的,例如:
- 遗留的老客户端系统,仍然处理的是XML格式的数据
- 要求处理速度快的这种客户端系统,一般要求返回JSON格式的数据
- 要求安全性高的客户端系统一般要求返回XML格式的数据
因此在现代的开发中不同的客户端可能需要后端系统返回不同格式的数据
总之后端应该满足这种多样化的需求
10.4.2、内容协商的两种方式
通常通过HTTP请求头(如 Accept)或请求参数(如 format)来指定客户端偏好接收的内容类型(如JSON、XML等)
服务器会根据这些信息选择最合适的格式进行响应
10.4.2.1、通过HTTP请求头
SpringBoot框架中在不做任何配置的情况下优先考虑的是这种方式
服务器会根据客户端发送请求时提交的请求头中的"Accept: application/json" 或 “Accept: application/xml” 或 "Accept: text/html"来决定响应什么格式的数据
客户端发送请求给服务器的时候有以下几种常见方式可以设置请求头的Accept
- 写代码
- fetch API
- ajax的XMLHttpRequest
- axios库
- jQuery库…
- 用工具
- 接口测试工具(例如:Postman、Apifox等)
- 命令行工具:curl
(1)、JSON格式
两种方式
- 使用@RestController标注类
- 使用@Controller标注类,同时使用@ResponseBody标注方法
【示例】在springboot-10-003模块中测试
JsonController.java
package com.longdidi.controller; import com.longdidi.bean.User1; import com.longdidi.service.UserService1; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController // 相当于@Controller + @ResponseBody,默认是将数据转换成json字符串进行响应 public class JsonController { @Autowired private UserService1 userService1; @GetMapping("/detail1") public User1 detail1() { return userService1.findUserById(); } }
User1.java
package com.longdidi.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User1 { private String username; private Integer age; }
UserService1.java
package com.longdidi.service; import com.longdidi.bean.User1; import org.springframework.stereotype.Service; @Service public class UserService1 { public User1 findUserById() { return new User1("jackson", 30); } }
使用了@RestController,也就是使用了@ResponseBody,因此默认支持的是返回JSON数据
测试http://localhost:8080/detail1
(2)、XML格式
第一步:引入一个依赖
com.fasterxml.jackson.dataformat jackson-dataformat-xml
第二步:在实体类上添加一个注解@JacksonXmlRootElement
package com.longdidi.bean; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @JacksonXmlRootElement public class User2 { private String username; private Integer age; }
第三步:编写service
package com.longdidi.service; import com.longdidi.bean.User2; import org.springframework.stereotype.Service; @Service public class UserService2 { public User2 findUserById() { return new User2("jackson", 30); } }
第四步:编写测试类
package com.longdidi.controller; import com.longdidi.bean.User2; import com.longdidi.service.UserService2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController // 相当于@Controller + @ResponseBody,默认是将数据转换成json字符串进行响应 public class XmlController { @Autowired private UserService2 userService2; @GetMapping("/detail2") public User2 detail2() { return userService2.findUserById(); } }
第五步:测试
使用curl命令行工具模拟发送请求并在请求头中设置Accept
curl -H “Accept: application/xml” http://localhost:8080/detail2
curl -H “Accept: application/json” http://localhost:8080/detail1
可以很清楚的看到服务器根据不同的请求头返回了不同格式的数据
- Accept: application/xml则返回XML格式的数据
- Accept: application/json则返回JSON格式的数据
10.4.2.2、通过参数format
使用请求参数的方式测试一下服务器的响应
注意:默认的请求参数名为format
仍然使用curl命令行工具进行测试:
curl http://localhost:8080/detail1?format=json
curl http://localhost:8080/detail2?format=xml
可以看到并没有达到想要的效果,这是因为SpringBoot优先考虑的不是通过请求参数format方式
想要实现优先使用format方式需要做如下配置:
# 内容协商时,优先考虑请求参数format方式。 spring.mvc.contentnegotiation.favor-parameter=true
再次测试:
curl http://localhost:8080/detail1?format=json
curl http://localhost:8080/detail2?format=xml
可以看到现在SpringBoot已经优先考虑使用请求参数format方式了
当然请求参数的名字可以不使用format,支持定制化
例如希望请求参数的名字为type,可以做如下配置:
# 内容协商时,设置请求参数的名字,默认为format spring.mvc.contentnegotiation.parameter-name=type
再次测试
curl http://localhost:8080/detail1?type=json
curl http://localhost:8080/detail2?type=xml
5、消息转换器
10.5.1、消息转换器接口
HttpMessageConverter接口被翻译为:Http消息转换器,它起到转换Http消息的作用
所谓的Http消息本质上就是浏览器向服务器发送请求时提交的数据,或者服务器向浏览器响应的数据
而HttpMessageConverter接口就是负责完成请求/响应时的数据格式转换的
在Spring MVC中提供了很多HttpMessageConverter接口的实现类,不同的Http消息转换器具有不同的转换效果,有的负责将Java对象转换为JSON格式的字符串,有的负责将Java对象转换成XML格式的字符串
(1)、常见的消息转换器
内置的常见的HttpMessageConverter的实现类包括
- 【请求】提交的表单(form)数据转换成Java对象的主要任务是由 FormHttpMessageConverter 消息转换器完成的
- 【请求】提交的JSON数据转换成Java对象的主要任务是由 MappingJackson2HttpMessageConverter 消息转换器完成的(通常使用@RequestBody注解)
- 【响应】将Java对象转换成JSON格式的数据,并将其写入HTTP响应体的任务是由 MappingJackson2HttpMessageConverter 消息转换器完成(通常使用的@ResponseBody注解)
- 【响应】将Java对象转换成XML格式的数据,并将其写入HTTP响应体的任务通常由 Jaxb2RootElementHttpMessageConverter 消息转换器完成
- 【响应】将 String 直接写入到响应体的任务是由 StringHttpMessageConverter 消息转换器完成
- …
(2)、请求消息转换器的确定
请求时通常根据以下条件来确定使用哪个消息转换器
- 请求的 Content-Type 字段
Spring MVC 会检查请求的 Content-Type 字段,以确定请求体的数据格式(例如 application/json、application/x-www-form-urlencoded、application/xml 等)
- 方法参数类型:
控制器方法中接收请求体的参数类型(例如 @RequestBody)
(3)、响应消息转换器的确定
响应时通常根据以下条件来确定使用哪个消息转换器
- 请求提交时请求头上的Accept字段
Spring MVC 会检查客户端请求的 Accept 字段,以确定客户端期望的响应格式(例如 application/json、application/xml 等)
- 方法返回值类型
控制器方法的返回值类型(例如 @ResponseBody)
例如1:@ResponseBody + 控制器方法的返回值是String,则使用StringHttpMessageConverter转换器(将字符串直接写入响应体)
例如2:@ResponseBody + 控制器方法的返回值是User,则使用MappingJackson2HttpMessageConverter转换器(将java对象转换成json格式的字符串写入到响应体)
(4)、系统消息转换器
查看源码
WebMvcAutoConfiguration.EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
WebMvcAutoConfiguration`内部类`EnableWebMvcConfiguration EnableWebMvcConfiguration`继承了`DelegatingWebMvcConfiguration DelegatingWebMvcConfiguration`继承了`WebMvcConfigurationSupport
在WebMvcConfigurationSupport类中有这样一个方法:addDefaultHttpMessageConverters() 用来添加默认的HttpMessageConverter对象。
通过断点调试可以发现默认支持6个HttpMessageConverter,如下:
这6个HttpMessageConverter作用如下:
- ByteArrayHttpMessageConverter:
用于将字节数组(byte[])与HTTP消息体之间进行转换。这通常用于处理二进制数据,如图片或文件。
- StringHttpMessageConverter:
用于将字符串(String)与HTTP消息体之间进行转换。它支持多种字符集编码,能够处理纯文本内容。
- ResourceHttpMessageConverter:
用于将Spring的Resource对象与HTTP消息体之间进行转换。Resource是Spring中表示资源的接口,可以读取文件等资源。这个转换器对于下载文件或发送静态资源有用。
- ResourceRegionHttpMessageConverter:
用于处理资源的部分内容(即“Range”请求),特别是当客户端请求大文件的一部分时。这对于实现视频流媒体等功能很有用。
- AllEncompassingFormHttpMessageConverter:
用于处理表单,是一个比较全面的form消息转换器。处理标准的application/x-www-form-urlencoded格式的数据,以及包含文件上传的multipart/form-data格式的数据。
- MappingJackson2HttpMessageConverter:
使用Jackson库来序列化和反序列化JSON数据。可以将Java对象转换为JSON格式的字符串,反之亦然。
另外通过以下源码也可以看到SpringBoot是根据类路径中是否存在某个类,而决定是否添加对应的消息转换器的
因此只要引入相关的依赖,让类路径存在某个类,则对应的消息转换器就会被加载
10.5.2、自定义消息转换器
可以看到以上6个消息转换器中没有yaml相关的消息转换器,可见如果要实现yaml格式的内容协商,yaml格式的消息转换器就需要我们自定义了
在测试模块springboot-10-003中测试
(1)、引入依赖
引入能够处理yaml格式的依赖
任何一个能够处理yaml格式数据的库都可以(这里选择使用jackson的库,因为它既可以处理json、xml、又可以处理yaml)
com.fasterxml.jackson.dataformat jackson-dataformat-yaml 2.18.0
(2)、编写消息转换器
编写类YamlHttpMessageConverter继承AbstractHttpMessageConverter,代码如下:
package com.longdidi.component; import com.fasterxml.jackson.databind.ObjectMapper; import com.longdidi.bean.User3; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import java.io.IOException; import java.nio.charset.Charset; // 这是一个消息转换器,专门处理yaml格式数据的 // 注意所有的消息转换器必须实现 HttpMessageConverter接口或者继承AbstractHttpMessageConverter抽象类,并实现/重写方法 public class YamlHttpMessageConverter extends AbstractHttpMessageConverter { // 对象映射器 private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)); // 非常重要的一步:要将媒体类型 text/yaml 和 消息转换器进行绑定 public YamlHttpMessageConverter() { super(new MediaType("text", "yaml", Charset.forName("UTF-8"))); } // 这个方法用来指定此消息转换器只适合于哪些类型的对象 @Override protected boolean supports(Class clazz) { // 表示只有User类型的对象才能够使用该消息转换器进行转换 return User3.class.isAssignableFrom(clazz); } // 这个是将yaml格式的字符串转换成java对象 // @RequestBody @Override protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } // 将java对象转换成yaml格式的字符串 // @ResponseBody @Override protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // ObjectMapper对象 objectMapper.writeValue(outputMessage.getBody(), o); } }
(3)、新增媒体类型YML
默认支持xml和json两种媒体类型,要支持yaml格式需要新增一个yaml媒体类型,在springboot的配置文件中进行如下配置:
# 新增一种媒体类型yaml # 媒体类型的名字可以自定义,不一定叫做yaml。具体的媒体类型值也是可以修改的,不一定是 text/yaml # 以下配置中媒体类型的名字是:yaml,媒体类型的值是:text/yaml # http://localhost:8080?format=yaml spring.mvc.contentnegotiation.media-types.yaml=text/yaml
注意,以上types后面的yaml是媒体类型的名字,名字随意,如果媒体类型起名为xyz,那么发送请求时的路径应该是这样的:http://localhost:8080/detail?format=xyz
(4)、配置消息转换器
重写WebMvcConfigurer接口的configureMessageConverters方法:
package com.longdidi.config; import com.longdidi.component.YamlHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { // 将新的消息转换器进行配置 @Override public void configureMessageConverters(List
- 写代码
-
- 效率比Ant高,一般新项目中使用PathPatternParser
-
-
-
-
-
-
- 运行时引入WebResourcesRuntimeHints这个类,这个类的作用是给JVM或者其他组件提示信息的,提示一下系统应该如何处理类和资源
- @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
- 用它来指定默认使用哪个转换器、默认使用哪个格式化器,在这个类当中都已经配好了