🤖🤖-摘要:
本文介绍了SpringBoot Web开发自动配置原理,包括web场景启动器的使用和自动装配功能,在web场景中添加特性如视图解析,静态资源处理,数据类型转换等。还有自定义静态资源的两种方式,通过配置文件或代码。

我的SpringBoot项目第四个模块

由前面SpringBoot快速入门分析可知,SpringBoot提出场景启动器的概念,
将场景中需要的所有依赖囊括进来,并自动装配,简化配置.
场景一引入,配置即完成

web开发同样需要web场景启动器

引入web场景启动器

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

spring-boot-starter-web依赖核心场景启动器:spring-boot-starter
spring-boot-starter依赖:spring-boot-autoconfigure
spring-boot-autoconfigure会加载:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中的自动配置类
其中web相关有如下:

org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
------------------------------以下是响应式编程相关--------------------------------------
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration

上面的自动配置类会绑定的配置有:

  • server ==>服务器相关
  • spring.mvc ==>springMVC相关
  • server.servlet.encoding ==>servlet编码相关
  • spring.servlet.multipart ==>servlet文件处理相关

SpringBoot 的自动装配功能在web场景中添加了如下特性:

官网介绍:

  1. Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  2. Support for serving static resources, including support for WebJars (covered later in this document).
  3. Automatic registration of Converter, GenericConverter, and Formatter beans.
  4. Support for HttpMessageConverters (covered later in this document).
  5. Automatic registration of MessageCodesResolver (covered later in this document).
  6. Static index.html support.
  7. Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

If you want to keep those Spring Boot MVC customizations and make more
MVC customizations (interceptors, formatters, view controllers, and other features),
you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

If you want to provide custom instances of RequestMappingHandlerMapping,
RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver,
and still keep the Spring Boot MVC customizations, you can declare a bean of
type WebMvcRegistrations and use it to provide custom instances of those components.

If you want to take complete control of Spring MVC,
you can add your own @Configuration annotated with @EnableWebMvc,
or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration
as described in the Javadoc of @EnableWebMvc

web场景引入后,就有了如下功能:

  1. 包含ContentNegotiatingViewResolverBeanNameViewResolver组件,方便视图解析
  2. 默认的静态资源处理机制: 静态资源放在static文件夹下即可直接访问
  3. 自动注册了Converter,GenericConverter,Formatter组件,适配常见数据类型转换格式化需求
  4. 支持HttpMessageConverters,可以方便返回json等数据类型
  5. 自动注册MessageCodesResolver,方便国际化及错误消息处理
  6. 支持静态 index.html
  7. 自动使用ConfigurableWebBindingInitializer,实现消息处理,数据绑定,类型转化,数据校验等功能

注意:

  • 如果想保持 SpringBoot MVC 的默认配置,并且自定义更多的 MVC 配置,如:interceptors,formatters,view controllers
    等.可以使用@Configuration注解添加一个WebMvcConfigurer类型的配置类,但不要标注@EnableWebMvc
  • 如果想保持 SpringBoot MVC
    的默认配置,但要自定义核心组件实例,比如:RequestMappingHandlerMapping,RequestMappingHandlerAdapter
    ,或ExceptionHandlerExceptionResolver,给容器中放一个WebMvcRegistrations组件即可
  • 如果想全面接管 Spring MVC,@Configuration标注一个配置类,并加上@EnableWebMvc注解,实现WebMvcConfigurer接口

接下来分别展开分析.

自动配置原理浅析

由前面知道引入web场景会加载自动配置类:WebMvcAutoConfiguration

下面简要分析WebMvcAutoConfiguration原理

生效条件

@AutoConfiguration(after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class}) // 需要在这三个自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) // //需要是普通的SERVLET类型的 web应用就生效,REACTIVE类型的响应式web是另一套
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) // 需要包括这三个类
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 需要容器中没有这个bean
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) // 优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
// 项目启动就注册的静态资源:"META-INF/resources/","resources/","static/","public/"
public class WebMvcAutoConfiguration {
//...
}

生效后, 在容器中放入两个bean:

HiddenHttpMethodFilter: 过滤页面表单提交的Rest请求

  • Rest 请求有: GET, POST, PUT, DELETE, PATCH
  • 请求数据包含三部分: 请求行, 请求头, 请求体
  • 浏览器只能发送GET请求和POST请求
  • GET请求没有请求体, 参数在请求行中,获取参数的方法是getQueryString()
  • POST请求的参数在请求体中, 获取方法是getReader()getInputStream()
  • getMethod() 获取具体哪种请求方式
/**
* 该方法将浏览器不支持的请求,转换成标准的HTTP方法:
*
* 将发出请求的方法参数转换为 HTTP 方法,可通过 HttpServletRequest.getMethod() 检索。
* 由于浏览器目前仅支持GET和POST,通常是使用带有附加隐藏表单字段(_method)的普通POST来传递“真正的”HTTP方法。
* 例如:<form action="..." method="post">
* <input type="hidden" name="_method" value="put" />
* ......
* </form>
* 此过滤器读取该参数并相应地更改 HttpServletRequestWrapper.getMethod() 返回值。
* 只允许使用“PUT”、“DELETE”和“PATCH” HTTP方法。
* 请求参数的名称默认为 _method,但可以通过 methodParam 属性进行调整。
*
* 注意:在大部分 POST 请求的情况下,此过滤器需要在 MultipartFilter 处理后运行,因为它本来就需要检查 POST 正文参数。
* 所以通常,在你的web.xml过滤器链中,在隐藏的HttpMethodFilter之前放置一个
* Spring org.springframework.web.multipart.support.MultipartFilter。
*
* 即在此过滤之前会有专门处理 POST 请求的 MultipartFilter.
*/
public class HiddenHttpMethodFilter extends OncePerRequestFilter {

private static final List<String> ALLOWED_METHODS =
List.of(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name());

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

HttpServletRequest requestToUse = request;

if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
// ...
}

FormContentFilter:用于分析表单内容, 与前面的过滤器配合使用,

同样只针对PUT,PATCH,DELETE三种HTTP请求

静态内部类WebMvcAutoConfigurationAdapter

WebMvcAutoConfiguration中有静态内部类WebMvcAutoConfigurationAdapter源码如下:

// Defined as a nested config to ensure WebMvcConfigurer is not read when not on the classpath
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
// ...
}

接口WebMvcConfigurer

WebMvcAutoConfigurationAdapter实现了WebMvcConfigurer接口,可以重写一些有关Web MVC的配置方法,比如添加拦截器、配置视图解析器、配置静态资源等.
通过重写这些方法,可以根据自己的需要定制化Web MVC的行为.可定制功能有:

WebMvcConfigurer
  • addArgumentResolvers:添加参数解析器
  • addCorsMappings:添加跨域映射
  • addFormatters:添加格式化器
  • addInterceptors:添加拦截器
  • addResourceHandlers:添加资源处理器,处理静态资源规则
  • addReturnValueHandlers:添加返回值处理器
  • addViewControllers:添加视图控制器,指定某个请求路径跳转到指定页面
  • configureAsyncSupport:配置异步支持
  • configureContentNegotiation:配置内容协商
  • configureDefaultServletHandling:配置默认的处理,默认接收: /
  • configureHandlerExceptionResolvers:配置异常解析器
  • configureMessageConverters:配置消息转化器
  • configurePathMatch:配置路径匹配
  • configureViewResolvers:配置视图解析器
  • extendHandlerExceptionResolvers:扩展处理异常解析器
  • extendMessageConverters扩展消息转换器
  • getMessageCodesResolver:获取消息编码解析器
  • getValidator:获取校验器

静态资源规则源码浅析

由上面分析,addResourceHandlers用来处理静态资源,源码:

private static final String SERVLET_LOCATION="/";
/**
* Add handlers to serve static resources such as images, js, and, css files from specific locations under web application root, the classpath,and others.
* 即根据配置情况,添加不同的静态资源处理器,用于处理静态资源的访问请求
*/
public void addResourceHandlers(ResourceHandlerRegistry registry){
// 判断是否需要启用默认的资源处理。
// 如果不需要启用,默认资源处理被禁用,同时输出调试信息,并直接返回
if(!this.resourceProperties.isAddMappings()){
logger.debug("Default resource handling disabled");
return;
}
// 需要启用默认资源处理,下面添加两种静态资源处理规则
// private String webjarsPathPattern = "/webjars/**";
// 访问路径:/webjars/**, 就去路径:classpath:/META-INF/resources/webjars/ 下面找对应资源
addResourceHandler(registry,this.mvcProperties.getWebjarsPathPattern(),
"classpath:/META-INF/resources/webjars/");
// private String staticPathPattern = "/**";
// private String[] staticLocations =
// "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/"
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);
}
});
}
/**
* 向资源处理器注册表中添加资源处理器,并根据配置文件和自定义函数设置资源处理器的属性
*/
private void addResourceHandler(ResourceHandlerRegistry registry,String pattern,Consumer<ResourceHandlerRegistration> customizer){
// 判断是否已经存在了指定的pattern
if(registry.hasMappingForPattern(pattern)){
return;
}
// 创建一个资源处理器注册(ResourceHandlerRegistration)对象
ResourceHandlerRegistration registration=registry.addResourceHandler(pattern);
// 将刚才创建的资源处理器注册对象作为参数,用于自定义资源处理器的属性
customizer.accept(registration);
// 根据配置文件中的缓存时间设置资源处理器的缓存时间(cache period),以及缓存控制(cache control)策略
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
// 进一步自定义资源处理器注册对象的属性
customizeResourceHandlerRegistration(registration);
}

小结一下:
静态资源访问规则如下:

  • 规则一 访问: /webjars/**路径就去classpath:/META-INF/resources/webjars/下找资源.
  • 规则二 访问: /**路径就去 静态资源默认的四个位置找资源
    • classpath:/META-INF/resources/
    • classpath:/resources/
    • classpath:/static/
    • classpath:/public/
  • 规则三 静态资源默认都有缓存规则的设置
    • 所有缓存的设置, 直接通过配置文件:spring.web
    • cachePeriod: 缓存周期; 多久不用找服务器要新的, 默认没有,以秒为单位
    • cacheControl: HTTP缓存控制, 查看文档
    • useLastModified:是否使用最后一次修改, 配合HTTP Cache规则, 如果浏览器访问了一个静态资源

如果浏览器访问了一个静态资源 index.js,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求

HTTP缓存实验

设置如下:

#1、spring.web:
# 1.配置国际化的区域信息(locale)
# 2.静态资源策略(开启、处理链、缓存)

spring:
web:
resources:
add-mappings: true # 开启静态资源映射规则,默认true
cache:
period: 3600 # 单位是 秒, 此为简要设置, 下面(cache-control)是详细配置,会覆盖period
cache-control:
max-age: 7200 # 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
use-last-modified: true # 默认true, 使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304

第一次请求:

第一次请求

第二次请求:

第二次请求

自定义静态资源

大体分为两种方式:

  • 配置文件的方式
  • 代码的方式

配置文件


@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
// ...
}

与两个配置文件绑定WebMvcProperties.classWebProperties.class,
即以spring.webspring.mvc开头的配置

  • spring.web:可配置locale(国际化)和resources(静态资源相关),具体可查看WebProperties.class
  • spring.mvc:可配置内容很多,具体可查看WebMvcProperties.class
spring:
web:
resources:
add-mappings: true # 开启静态资源映射规则,默认true
cache:
period: 3600 # 单位是 秒, 此为简要设置, 下面(cache-control)是详细配置,会覆盖period
cache-control:
max-age: 7200 # 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
use-last-modified: true # 默认true, 使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
static-locations: classpath:/static/,classpath:/test/ # 自定义静态资源目录,按顺序访问
mvc:
webjars-path-pattern: /webjars/** # 自定义webjars路径前缀,默认:/webjars/**
static-path-pattern: /static/** # 静态资源访问路径前缀,默认:/**

代码方式

就是在容器中放置组件:WebMvcConfigurer,来配置底层.

注意:

  • 默认配置仍有效
  • 加上@EnableWebMvc会将默认配置失效

@Configuration
public class MyConfig {
@Bean
public WebMvcConfigurer webMvcConfigurer() {// 与下面代码一样效果
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static");
}
};
}
}
@Configuration
public class MyConfig implements WebMvcConfigurer { // 还可以上面写法,效果一样
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 保留默认配置
WebMvcConfigurer.super.addResourceHandlers(registry);

// 自定义配置
registry.addResourceHandler("/static/**")// 设置静态资源访问前缀,同配置文件中的spring.mvc.static-path-pattern:
.addResourceLocations("/static");// 设置静态资源获取路径,同配置文件中的spring.web.resources.static-locations: classpath:/static/

}
}
为什么容器中含有WebMvcConfigurer组件,就能配置底层行为???
  1. WebMvcAutoConfiguration是一个自动配置类, 它里面有一个EnableWebMvcConfiguration
  2. EnableWebMvcConfiguration继承与DelegatingWebMvcConfiguration, 这两个都生效
  3. DelegatingWebMvcConfiguration利用DI把容器中所有WebMvcConfigurer注入进来
  4. 当调用DelegatingWebMvcConfiguration的方法配置底层规则, 而它调用所有WebMvcConfigurer的配置底层方法