SpringBoot2之web开发(上)——之静态资源和请求参数处理
Posted AC_Jobim
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot2之web开发(上)——之静态资源和请求参数处理相关的知识,希望对你有一定的参考价值。
SpringBoot2之web开发(上)——之静态资源和请求参数处理
环境:SpringBoot 2.5.2
一、SpringMVC自动配置概览
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
-
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 内容协商视图解析器和BeanName视图解析器
-
Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源(包括webjars)
-
Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- 自动注册
Converter,GenericConverter,Formatter
- 自动注册
-
Support for
HttpMessageConverters
(covered later in this document).- 支持
HttpMessageConverters
(后来我们配合内容协商理解原理)
- 支持
-
Automatic registration of
MessageCodesResolver
(covered later in this document).- 自动注册
MessageCodesResolver
(国际化用)
- 自动注册
-
Static
index.html
support.- 静态index.html 页支持
-
Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).- 自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
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 typeWebMvcConfigurer
but without@EnableWebMvc
.
不用@EnableWebMvc注解。使用@Configuration
+WebMvcConfigurer
自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.
声明WebMvcRegistrations
改变默认底层组件
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
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.
使用@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
二、简单功能分析
2.1 静态资源访问
-
静态资源目录:
默认情况下,我们只需要将静态资源放在一下几个目录中就可以直接通过url在浏览器中访问了。
/META-INF/resources/
/resources/
/static/
/public/
静态资源的映射为:
/**
,当请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面访问 :当前项目根路径/ + 静态资源名
静态资源的默认访问优先级:/META-INF/resources/
>/resources/
>/static/
>/public/
-
更改默认的静态资源路径和静态资源访问前缀:
spring: mvc: static-path-pattern: /res/** #修改静态资源访问前缀 # 此时,当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找 web: resources: static-locations: classpath:/haha/ #更改默认的静态资源路径
-
webjar
- 所有
/webjars/**
,都去classpath:/META-INF/resources/webjars/
找资源 - webjars:以jar包的方式引入静态资源;可用jar方式添加css,js等资源文件,将自动映射
/webjars/**
- webjars官网
例如:添加jquery
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.5.1</version> </dependency>
访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源,我们这里访问地址对应就是:
http://localhost:8080/webjars/jquery/3.5.1/jquery.js
- 所有
2.2 欢迎页支持
- 欢迎页,静态资源文件夹下的所有 index.html 页面;被
/**
映射。 - 比如我访问
http://localhost:8080/
,就会找静态资源文件夹下的index.html
欢迎页支持注意:
-
可以配置静态资源路径
-
但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring: # mvc: # static-path-pattern: /res/** # 这个会导致welcom page功能失效 web: resources: static-locations: classpath:/haha/
2.3 自定义Favicon
- 与其他静态资源一样,Spring Boot在配置的静态内容位置中查找 favicon.ico。如果存在这样的文件,它将自动用作应用程序的favicon。
- 所有的
favicon.ico
都是在静态资源文件下找;
使用自定义Favicon注意:
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
2.4 静态资源配置原理(源码分析)
好的博客:SpringBoot静态资源配置原理(源码一步步分析,详细易懂)
-
SpringMVC功能的自动配置类
WebMvcAutoConfiguration
,生效@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { }
-
WebMvcAutoConfiguration类中的
WebMvcAutoConfigurationAdapter静态内部类
该类为配置类,配置文件的相关属性和xxx进行了绑定
-
WebMvcAutoConfigurationAdapter
只有一个有参构造器当配置类
只有一个有参数的构造器
,它的有参构造器中所有参数的值都会从容器中确定
。public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources()); this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.dispatcherServletPath = dispatcherServletPath; this.servletRegistrations = servletRegistrations; this.mvcProperties.checkConfiguration(); }
它的参数:
- 第一个参数是ResourceProperties resourceProperties ,获取和spring.resources绑定的所有的值的对象
- 第二个参数是WebProperties webProperties ,获取和spring.web绑定的所有的值的对象
- 第三个参数是WebMvcProperties mvcProperties,获取和spring.mvc绑定的所有的值的对象
- 第四个参数是ListableBeanFactory beanFactory ,这个是Spring的beanFactory,也就是我们的容器。
- 第五个参数是ObjectProvider messageConvertersProvider,找到所有的HttpMessageConverters
- 第六个参数是ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,找到资源处理器的自定义器
- 第七个参数是ObjectProvider dispatcherServletPath,相当与找dispatcherServlet能处理的路径
- 第八个参数是ObjectProvider<ServletRegistrationBean<?>> servletRegistrations ,给应用注册原生的Servlet、Filter等等
2.4.1 addResourceHandlers方法(静态资源处理默认规则)
所有的静态资源处理默认规则都在addResourceHandlers方法中
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {//当isAddMappings()方法返回false,禁用掉静态资源的路径映射
logger.debug("Default resource handling disabled");
} else {
//1、添加webjars资源映射规则
this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
//2、添加静态资源映射规则**/
//this.mvcProperties.getStaticPathPattern() =====> '/**',spring.mvc.static-path-pattern设置静态资源的访问前缀
//this.resourceProperties.getStaticLocations() =====> new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"}
//spring.web.resources.static-locations,设置静态资源的存放目录
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
registration.addResourceLocations(new Resource[]{resource});
}
});
}
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
this.addResourceHandler(registry, pattern, (registration) -> {
registration.addResourceLocations(locations);
});
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
if (!registry.hasMappingForPattern(pattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
customizer.accept(registration);
//spring.web.resources.cache.period,设置静态资源的缓存时间
registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
this.customizeResourceHandlerRegistration(registration);
}
}
首先调用resourcePropertoes的isAddMappings()方法,当isAddMappings()方法返回false,禁用掉静态资源的路径映射
spring:
resources:
add-mappings: false 禁用所有静态资源规则
静态资源映射默认路径和静态资源默认存放目录:
2.4.2 欢迎页处理规则
HandlerMapping
:处理器映射。保存了每一个Handler能处理哪些请求
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping方法:
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
if (welcomePage != null && "/**".equals(staticPathPattern)) {
//要用欢迎页功能,必须是/**
logger.info("Adding welcome page: " + welcomePage);
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
//调用Controller
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
三、请求参数处理
3.1 请求映射
3.1.1 rest使用与原理
传统CRUD请求方式:
/getUser?id=1 查询用户
/deleteUser?id=1 删除1号用户
/updateUser?id=1 更新1号用户
/addUser 添加用户
REST风格请求方式:
/user/1 GET-----查询1号用户
/user/1 PUT------更新1号用户
/user/1 DELETE-----删除1号用户
/user POST-----添加用户
rest使用:
-
开启页面表单的Rest功能
spring: mvc: hiddenmethod: filter: enabled: true #开启页面表单的Rest功能
-
页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
-
编写Controller请求映射,使用
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
或者使用@RequestMapping指定method属性
代码示例:
测试REST风格:
<form action="/user" method="get">
<input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input value="REST-PUT 提交" type="submit"/>
</form>
@RestController
public class HelloController {
@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
}
rest原理(源码分析):
表单如何发出delete和put请求
?
因为我们的表单提交的请求方式中只支持
GET
和POST
方式的请求,它不支持DELETE和 PUT 方式的请求。
然而我们以前用SpringMVC来完成这些事情,我们需要配置一个叫
HiddenHttpMethodFilter
的Filter;但现在Spring Boot已经帮我们配置好了,我们只需要设置是否开启。
在WebMvcAutoConfiguration中,可以看到它已经配置了一个HiddenHttpMethodFilter,如下:
设置开启表单的rest风格:
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
开启之后所有的请求都要经过HiddenHttpMethodFilter类
的doFilterInternal方法
Rest原理总结:(表单提交要使用REST的时候)
- 表单提交会带上 _method=PUT
- 请求过来被
HiddenHttpMethodFilter
拦截-
请求是否正常,并且是POST
-
获取到 _method的值。
-
兼容以下请求;PUT.DELETE.PATCH
-
原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值。
-
过滤器链放行的时候用包裹过的请求。以后的方法调用getMethod是调用requestWrapper的。
-
3.1.2 请求映射原理(源码分析)
好的博客:SpringMVC请求映射原理
3.1.2.1 DispatcherServlet
SpringBoot底层还是使用的SpringMVC,所以请求过来时,都会到达DispatcherServlet
.
DispatcherServlet的继承树
DispatcherServlet间接继承了HttpServlet,那么一定会重写doGet()和doPost()方法,而DispatcherServlet类中没有重写doGet()和doPost()方法。当FrameworkServlet
类中是否重写了。
四个方法都调用了processRequest()
方法
processRequest()方法调用了父类DispatcherServlet的doService()
方法
doService()
方法中又调用了doDispatch()
方法,所以,对于每个请求进来,都会调用org.springframework.web.servlet.DispatcherServlet
的doDispatch()
这个方法来处理请求
3.1.2.2 doDispatch()方法
在doDispatch方法处加上断点,开启debug
发送一个原生的get请求,在idea中可以看到发送请求的路径为/user
3.1.2.3 HandlerMapping处理器映射
进入getHandler(processedRequest)方法,获取了handlerMappings,这是处理器映射。其中又5个HandlerMapping
WelcomePageHandlerMapping
,欢迎页的处理器映射
RequestMappingHandlerMapping
,这其中保存了所有@RequestMapping
注解和handler的映射规则,在SpringBoot启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在这个映射处理器中。可以将这个HandlerMapping理解为一个Map,其中key为请求路径,value为handler的处理方法
3.1.2.4 HandlerMapping的getHandler()方法
进入getHandler()方法,直到进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping的lookupHandlerMethod()
方法。该方法获得目标HandlerMethod对象。
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>(); // 存储匹配到的结果
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); // 关键:根据请求路径找到直接匹配的结果(只根据路径名匹配)
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request); // 关键:从直接匹配的结果中寻找并将最终结果存入matches中(根据请求方法匹配)
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0); // 获取结果集中的第1个值作为最佳匹配
if (matches.size() > 1) { // 如果找到了多个匹配的值
ComparatorSpringBoot2.x应用之手工创建web应用
Springboot2之静态资源规则与定制化welcome与favicon功能Rest映射及源码解析以及改变默认的_method
Springboot2之静态资源规则与定制化welcome与favicon功能Rest映射及源码解析以及改变默认的_method