Spring Boot学习笔记总结

Posted IT_Holmes

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Boot学习笔记总结相关的知识,希望对你有一定的参考价值。

文章目录

1. Web请求参数处理 Rest映射源码解析

1.1 OrderedHiddenHttpMethodFilter对象原理


Rest风格:

  • 注意这里是Rest风格,并不是Restful风格不要混淆!
  • 可以直接认为Rest风格就是@RestController和@ResponseBody。
package com.itholmes.boot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

//RestController就是rest风格,还有@Responsebody
@RestController
public class HelloController 
	
	//相同的路径/user,来处理不同的请求类型。
    @RequestMapping(value = "/user" ,method = RequestMethod.GET)
    //等同于@GetMapping("/user")
    public String getUser()
        return "GET-张三";
    

    @RequestMapping(value = "/user" ,method = RequestMethod.POST)
     //等同于@PostMapping("/user")
    public String saveUser()
        return "POST-张三";
    

    @RequestMapping(value = "/user" ,method = RequestMethod.PUT)
     //等同于@PutMapping("/user")
    public String putUser()
        return "PUT-张三";
    

    @RequestMapping(value = "/user" ,method = RequestMethod.DELETE)
     //等同于@DeleteMapping("/user")
    public String deleteUser()
        return "DELETE-张三";
    


对于rest风格的参数类型处理,必须要给容器中注入一个OrderedHiddenHttpMethodFilter对象,这个对象也是在WebMvcAutoConfiguration类自动配置注入。

在WebMvcAutoConfiguration类下,有一个hiddenHttpMethodFilter(排序隐藏Http方法过滤器)方法,这个方法的作用是过滤请求参数的方法类型,例如:get,post,delete,put等等。注意这个hiddenHttpMethodFilter装配到容器中,该对象本身是一个filter过滤器!

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(
    prefix = "spring.mvc.hiddenmethod.filter",
    name = "enabled"
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() 
    return new OrderedHiddenHttpMethodFilter();

但是在@ConditionalOnProperty注解中有一个matchIfMissing注解属性,默认是false的!这个属性的功能如下:

/**
 *该属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功
 */
boolean matchIfMissing() default false;

因为是matchIfMissing默认是false,所以开始的HiddenHttpMethodFilter过滤器是没有装配到容器中的,因此我们要设置spring.mvc.hiddenmethod.filter.enabled=true来启动该过滤器(装配到容器中)。

可以理解的是spring.mvc.hiddenmethod.filter.enabled=true是专门用来处理表单的Rest功能。

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true # 启用HiddenHttpMethodFilter过滤器,专门用来处理表单传送过来的_method的参数数据,进而来出来delete,put等类型参数。

1.2 表单提交要使用Rest的使用


对应上面内容。

这样我们在前端发送不同类型,后台就可以处理了:

  • 表单提交要带上_method=PUT , 这样的方式来能让SpringBoot自动配置的hiddenHttpMethodFilter方法识别到!
  • 再次提醒,不要忘记启动hiddenHttpMethodFilter组件对象,也就是必须要装配到容器中(被启动)。因为上面的这种方式必须基于该组件对象。
测试REST风格:
<form action="/user" method="get">
    <input type="submit" value="REST-GET">
</form>
<form action="/user" method="POST">
    <input type="submit" value="REST-POST">
</form>
<!--
    form表单是不能提交delete,put等类型的,直接定义这些类型,form表单还是走默认的get方法。
    根据hiddenHttpMethodFilter方法源码:通过传递一个name为"_method"值的参数来指定,并且方法的类型必须是post方法,因为它的源码规定这里必须是post方式!。
-->
<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input type="submit" value="REST-delete">
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT">
    <input type="submit" value="REST-put">
</form>

hiddenHttpMethodFilter在容器中本质是一个OrderedHiddenHttpMethodFilter对象,该对象又继承了HiddenHttpMethodFilter对象,在HiddenHttpMethodFilter对象中,有一个doFilterInternal方法:

private static final List<String> ALLOWED_METHODS;

static 
	//这里定义支持哪几种请求,有delete,put,patch这三种。也就是允许这三种请求。
    ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));



protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException 
	//拿到当前的请求request
    HttpServletRequest requestToUse = request;
    //先判断是不是post方法,也说明了前面form表单传递_method=PUT形式为什么要用post,这里规定的就是post。并且判断请求是否正常,有没有error。
    if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) 
    	//在request获取参数,这里的this.methodParam是整个类写死的一个值就是"_method";
    	//也就是在request中获取到了_method的参数了,也就是form表单传的delete,put等等。
        String paramValue = request.getParameter(this.methodParam);
        //这里判断有没有长度,也就是判断是不是空
        if (StringUtils.hasLength(paramValue)) 
        	//将值进行一个大写转换。也就迎合了前端发送大写小的方法名都可以。
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            //对应上面ALLOWED_METHODS,判断是否属于ALLOWED_METHODS,仅能支持delete,put,patch。
            if (ALLOWED_METHODS.contains(method)) 
            	//将原生request(post请求) , 包装模式requestWrapper重写了getMethod方法,返回的是重写后的request(就是这里的method请求[delete,put,patch请求])
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            
        
    
	//之后就是将处理后的requestToUse放行了。过滤器放行。
    filterChain.doFilter((ServletRequest)requestToUse, response);


==========================================================================================
//对应上面的HttpMethodRequestWrapper
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper 
    private final String method;

    public HttpMethodRequestWrapper(HttpServletRequest request, String method) 
        super(request);
        this.method = method;
    
	//这里将request的值重写了,也就是原来是request的method是post,现在重写为了根据_method传入的类型(delete,put,patch)
    public String getMethod() 
        return this.method;
    

这样也就将原来的post请求类型,通过OrderedHiddenHttpMethodFilter过滤器转成了不同的请求类型。

1.3 Rest使用客户端工具发送不同类型的请求


例如:使用postman发送的put参数。

也就是不影响原生的功能。

1.4 如何改变默认的_method?


自己创建一个HiddenHttpMethodFilter对象,放入容器中。


自己写一个配置类,注入到容器中:

package com.itholmes.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration(proxyBeanMethods = false)
public class WebConfig 

    @Bean
    //注意这里有两个HiddenHttpMethodFilter对象,一个是普通servlet使用的,一个是响应式使用的。
    public HiddenHttpMethodFilter hiddenHttpMethodFilter()
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        //设置为自己想要的名字。
        hiddenHttpMethodFilter.setMethodParam("_m");
        return hiddenHttpMethodFilter;
    


2. Web请求参数处理 请求映射原理


DispatcherServlet类实现的过程,例如,实现get方法的过程,也是从上往下:

SpringMvc功能分析都从org.springframework.web.servlet.DispatcherServlet =》doDispatch()方法实现。

分析doDispatch方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception 
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try 
        try 
            ModelAndView mv = null;
            Object dispatchException = null;

            try 
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

				//找到当前请求使用哪个Handler(Controller的方法)处理。
                mappedHandler = this.getHandler(processedRequest);

=================================================================
                
//查看getHandler方法,里面有一个HandlerMapping处理器映射。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception 
    if (this.handlerMappings != null) 
        Iterator var2 = this.handlerMappings.iterator();

        while(var2.hasNext()) 
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) 
                return handler;
            
        
    

    return null;

通过发送一个路径请求,查看handlerMappings中的内容:

  • RequestMappingHandlerMapping作用就是处理所有@RequestMapping注解 和 handler的映射规则。
  • WelcomePageHandlerMapping是处理欢迎页功能的映射。


SpringBoot自动配置欢迎页的WelcomePageHandlerMapping的源码分析(访问 " / " 能访问到index.html):

因为这里while循环的遍历,依次尝试所有的HandlerMapping看是否有对应路径的请求信息

  • 如果找到了对应的请求handler就会return handler。
  • 如果没有找到就继续遍历下一个handler。

总结起来就是知道RequestMappingHandlerMappin和WelcomePageHandlerMapping在SpringMvc中什么作用!剩余的三个了解

此外,还可以自定义映射处理,我们也可以自己给容器中放HandlerMapping。自定义HandlerMapping。

3. 请求处理常用的参数注解

3.1 SpringMvc常用的一些注解


@PathVariable的用法:

  • @PathVariable(“id”) Integer id://@PathVariable(“id”)拿到对应的值。
  • @PathVariable Map<String,String> path_value://拿到所有的@PathVariable注解对应的key-value(也就是指定@PathVariable注解的value拿指定的,没有指定就拿所有和@PathVariable绑定的key-value。)

@RequestHeader的用法:

  • @RequestHeader(“User-Agent”) String userAgent://@RequestHeader注解拿到请求头的信息。
  • @RequestHeader Map<String,String> headers://和上面一样,拿到所有的请求头信息。指定@RequestHeader注解的value值就拿到对应指定的,没有指定就拿全部的。

@RequestParam的用法:

  • @RequestParam(“age”) Integer age://接受form表单格式单个。
  • @RequestParam(“inters”) List inters://当form表单对一个key指定了多个value可以使用list来接受,例如: ?inters=1&inters=2&inters=4。
  • @RequestParam Map<String,String> params://和上面一样,获取全部的form表单形式的参数。

@CookieValue的用法:

  • @CookieValue(“token”) String value://在cookie中拿到token的value值。
  • @CookieValue(“token”) Cookie cookie://拿到token的整个cookie。

@RequestBody的用法:

  • @RequestBody String content://如果是form表单提交,例如:name=zhangsan&pwd=123123 , 这里的content就会变成“name”:“zhangsan”,“pwd”:“123123”

@RequestAttribute的用法:

3.2 SpringBoot的矩阵变量 和 UrlPathHelper类


什么是矩阵变量,矩阵变量以及对比其他的方式?

  • /cars/path?xxx=xxx&aaa=ccc 这可以是queryString查询字符串,后台用@RequestParam接受。
  • /cars/sell;low=34;brand=byd,audi,yd 使用" ; "的这种形式叫做矩阵变量。

页面开发,cookie禁用了,session里面的内容怎么使用?

  • 可以使用url重写:/abc;jsessionid=xxxx 把cookie的值使用矩阵变量的方式进行传递。

/boss/1/2 对比 /boss/1;age=20/2;age=18 分号;前面是路径,后面是矩阵变量。


SpringBoot默认是禁用了矩阵变量的功能。

原理:在WebMvcAutoConfiguration中,有一个configurePathMatch方法,在该方法中有一个UrlPathHelper(Url路径帮助器) 类,在这个类中,有一个removeSemicolonContent(Semicolon分号)成员变量属性,这个属性就是规定SpringBoot支不支持矩阵变量的(也就是是否移除分号后面的内容。)。

UrlPathHelper类有很多的功能:


如何开启SpringBoot支持矩阵变量的功能?

  • 可以在配置类中自定义配置WebMvcConfigurer容器对象,来修改UrlPathHelper对象中的removeSemicolonContent变量。
package com.itholmes.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

@Configuration(proxyBeanMethods = false)
/*
    第一种方式:直接implement的WebMvcConfigurer。
        并且重写configure
*/
public class WebConfig implements WebMvcConfigurer 

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) 
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        //设置为false,也就是不移除分号后面的内容,这样矩阵变量可以生效。
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    

    /*
        第二种方式:
            使用@Bean,直接向容器中装配一个WebMvcConfigurer。

        @Bean
        public WebMvcConfigurer webMvcConfigurer()
            return new WebMvcConfigurer() 
                @Override
                public void configurePathMatch(PathMatchConfigurer configurer) 
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    //设置为false,也就是不移除分号后面的内容,这样矩阵变量可以生效。
                    urlPathHelper.setRemoveSemicolonContent(false);
                    configurer.setUrlPathHelper(urlPathHelper);
                
            ;
        
    */


获取矩形变量也有格式上的一些要求:

  • 例如: “/cars/path” 。
  • @PathVariable(“path”) String path拿到的就是path里面分号前面的路径。
  • @MatrixVariable(“xxx”) 拿到的就是矩形变量xxx对应的内容。
 /*
    矩形变量注意点:
        1.SpringBoot默认是禁用了矩形变量的功能。
        2.矩形变量必须有url路径变量才能被解析,也就是/cars/sell;low=34 => path sell就是路径,low就是矩形变量。
*/

//第一种情况: 一个key对应一个值,一个key对应多个值。
// 例如矩阵变量的语法为: /cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/path")
public Map carsSell(
        //获取path的路径。
        @PathVariable("path") String path,
        //拿到path中的矩阵变量。
        @MatrixVariable("low") Integer low,
        @MatrixVariable("brand") List<String> brand
)
    HashMap<String, Object> map = new HashMap<>();
    map.put("low",low);
    map.put("brand",brand);
    map.put("path",path);
    return map;


//第二种情况: 两个下的相同key。
// /boss/1;age=20/2;age=18
@GetMapping("/boss/bossId/empId")
public Map boss(
    //通过pathVar来指定获取哪一个的age。
    @MatrixVariable(value = "age" , pathVar = "bossId") Integer bossAge,
    @MatrixVariable(value = "age", pathVar = "empId") Integer empAge
)
    HashMap<String, Object> map = new HashMap<>();
    map.put("bossAge",bossAge);
    map.put("empAge",empAge);
    return map;

4. 请求参数处理 类型参数的解析原理

4.1 找到合适的HandlerAdapter处理器的源码分析


同样和HandlerMapping一样,HandlerAdapter也会是在DispatcherServlet的doDispatch方法中运行:

接下来说的是Handler适配器,HandlerAdapter如何处理一些参数。

对于各种类型参数的解析原理,使用的是适配器HandlerAdapter来解决。

  • handlerMapping中找到能处理请求的handler(controller)。为当前Handler(controller)找一个适配器HandlerAdapter。


上图是SpringMvc默认的4个HandlerAdapter,handler适配器:

  • 0 - RequestMappingHandlerAdapter是支持方法上标注@RequestMapping。
  • 1 - HandlerFunctionAdapter是支持函数式变成的。

执行的核心又在这里:

//拿到能处理的handlerAdapter
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
//在这里执行handlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

4.2 HandlerAdapter的执行目标方法


//在这里执行handlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mappedHandler.getHandler()的源码解析:

handle方法源码:

继续step into深入,就会看到继承了abstractHandlerMethodAdapter的RequestMappingHandlerAdapter类,在该类有一个handleInternal方法,该方法如下代码:

//这段代码就是来执行目标方法的。就是controller那些类里面

以上是关于Spring Boot学习笔记总结的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot学习笔记总结

Spring Boot学习笔记总结

看完这份学习笔记,Spring Boot于你而言就是小菜一碟

Spring Boot学习笔记:简介与HelloWorld搭建

Spring Boot学习总结一

Spring Boot学习笔记