SpringBoot2之web开发(上)——之静态资源和请求参数处理

Posted AC_Jobim

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 and BeanNameViewResolver beans.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter 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 type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

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.
声明 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-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

二、简单功能分析

2.1 静态资源访问

  1. 静态资源目录:

    默认情况下,我们只需要将静态资源放在一下几个目录中就可以直接通过url在浏览器中访问了。

    • /META-INF/resources/
    • /resources/
    • /static/
    • /public/

    静态资源的映射为:/**,当请求进来,先去找Controller看能不能处理不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

    访问 :当前项目根路径/ + 静态资源名

    静态资源的默认访问优先级/META-INF/resources/>/resources/>/static/>/public/


  1. 更改默认的静态资源路径和静态资源访问前缀:

    spring:
      mvc:
        static-path-pattern: /res/**  #修改静态资源访问前缀
        # 此时,当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
    	
      web:
        resources:
          static-locations: classpath:/haha/  #更改默认的静态资源路径
    
  2. 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

欢迎页支持注意:

  1. 可以配置静态资源路径

  2. 但是不可以配置静态资源的访问前缀。否则导致 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静态资源配置原理(源码一步步分析,详细易懂)

  1. 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 {
    }
    
  2. WebMvcAutoConfiguration类中的WebMvcAutoConfigurationAdapter静态内部类

    该类为配置类,配置文件的相关属性和xxx进行了绑定

  3. 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();
    }
    

    它的参数:

    1. 第一个参数是ResourceProperties resourceProperties ,获取和spring.resources绑定的所有的值的对象
    2. 第二个参数是WebProperties webProperties ,获取和spring.web绑定的所有的值的对象
    3. 第三个参数是WebMvcProperties mvcProperties,获取和spring.mvc绑定的所有的值的对象
    4. 第四个参数是ListableBeanFactory beanFactory ,这个是Spring的beanFactory,也就是我们的容器。
    5. 第五个参数是ObjectProvider messageConvertersProvider,找到所有的HttpMessageConverters
    6. 第六个参数是ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,找到资源处理器的自定义器
    7. 第七个参数是ObjectProvider dispatcherServletPath,相当与找dispatcherServlet能处理的路径
    8. 第八个参数是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使用:

  1. 开启页面表单的Rest功能

    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true #开启页面表单的Rest功能
    
  2. 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)

  3. 编写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请求

因为我们的表单提交的请求方式中只支持GETPOST方式的请求,它不支持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.DispatcherServletdoDispatch()这个方法来处理请求

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应用

Python之HTTP静态Web服务器开发

Springboot2之静态资源规则与定制化welcome与favicon功能Rest映射及源码解析以及改变默认的_method

Springboot2之静态资源规则与定制化welcome与favicon功能Rest映射及源码解析以及改变默认的_method

八SpringBoot2核心技术——web开发(静态资源访问)

八SpringBoot2核心技术——web开发(静态资源访问)