尚硅谷springboot2day3

今天是2021-1-25。

一。rest风格请求

1.rest风格下的http请求:get–获取,post–保存,delete–删除,put–修改,但是实际上put和delete不是那么好发。
1.2.要开启表单提交支持delete和put请求,需要配置spring.mvc.hiddenmethod.filter.enabled=true
1.3.表单提交时如下:

  <form method="post">
  <input  name="_method" type="hidden" value="delete"  />
  ......
  </form>

如果请求方式为post,就会获取名为_method的隐藏域的值,实际的请求方式就是这个值。表单因为只支持get和post,所以要开启这样的支持,而如果是客户端直接以put请求去请求服务器,不需要开启支持。

二。请求映射原理

1.SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
2.请求解析流程:
2.1获取所有的handlermapping,循环遍历,看哪个handlermapping包含匹配的请求,如果在某个handlermapping中找到了匹配的请求,就返回HandlerExecutionChain对象:包含请求对应的handler–处理方法,以及这个handler所在的拦截器链

在这里插入图片描述
在这里插入图片描述

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping mapping : this.handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

2.2可以自定义handlermapping
2.3为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter,大多数时候都是这个,标注了@requestmapping系列注解的都会使用这个adapter:
在这里插入图片描述
与寻找handler的过程类似,获取了所有的HandlerAdapter,遍历查看那个adapter适合处理当前handler。
2.3.1在真正执行目标方法之前,先要执行该handler所在拦截器链的所有prehandle方法,拿到拦截器链的集合后遍历集合执行:
(1)如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
(2)如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion方法;
(3)如果任何一个拦截器返回false。直接跳出不执行目标方法
(4)所有拦截器都返回True。可以接着执行目标方法
2.4找到合适的适配器后,用该适配器执行当前的handler,其实是执行的HandlerExecutionChain对象内部包含的handler:

// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

2.4.1在handle方法内部执行这个handler对应的方法

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法

2.4.1.1在执行之前,为当前的目标方法设置参数解析器,总共26种。确定将要执行的目标方法的每一个参数的值是什么;SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
在这里插入图片描述
2.4.1.2设置完参数解析器后,为当前目标方法设置返回值处理器,总共15种:
在这里插入图片描述
2.4.2为当前目标方法的对象设置完方法执行需要的组件后,创建ModelAndViewContainer,存储目标视图的路径和渲染目标视图需要的模型数据。当前目标方法对象调用方法invokeAndHandle(),并传入ModelAndViewContainer,开始执行
2.4.2.1在其中,调用invokeForRequest真正开始执行

//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

2.4.2.1.1在其中

//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

获取方法所有的参数值
2.4.2.1.1.1

============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                // Leave stack trace for later, exception may actually be resolved and handled...
                if (logger.isDebugEnabled()) {
                    String exMsg = ex.getMessage();
                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                        logger.debug(formatArgumentError(parameter, exMsg));
                    }
                }
                throw ex;
            }
        }
        return args;
    }
 @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }

其实参数集合args一开始是空的,首先要判断当前参数是否可以被参数解析器解析,这个判断的过程其实就是获取当前方法之前设置的所有参数解析器,循环遍历判断哪个适合解析这个参数。返回合适的解析器后,调用该解析器的解析方法解析当前参数,为args[i]赋值。最后循环获得每个方法的参数后,返回args。
2.4.2.1.2获取到方法所有的参数值args后,所有的参数都会被放入ModelAndViewContainer中的defaultModel中,作为模型数据,在渲染目标视图的时候放入请求域中,就比如自定义实体类类型的参数,在解析完成后就会被放入defaultmodel中。接着反射调用目标方法。
2.4.2.2获取到方法执行后的返回值,处理返回值,如果返回值是一个字符串,就认为这是你将要访问的视图的逻辑视图名,将其放入ModelAndViewContainer:mavContainer.setViewName(str),这就是为什么我们控制器的方法返回字符串可以跳转到某个视图的原因。
2.4.3要跳转的视图确定后,准备返回modelandview对象。首先更新defaultmodel,先判断此时是否设置session的状态为completed,如果是,就清除由@SessionAttritubtes设置到session中的数据,也同步清除存放在defaultmodel中的数据。
2.4.3.1获取ModelAndViewContainer的逻辑视图名viewname和模型数据defaultModel,将这两个参数封装到ModelAndView对象中,最后返回ModelAndView对象,可以看到,无论你的返回值是什么,最后都会返回ModelAndView对象,只不过相关数据被封装到model里面了而已。到此,一个handler就算是执行完毕了,如同我们熟知的springmvc执行流程一样,handler执行完毕后,返回了ModelAndView对象。
2.5获取方法所在的拦截器链集合,倒序执行所有拦截器的postHandle方法。
2.6准备渲染目标视图:将modelandview对象中的viewname和model取出。
2.6.1将viewname交给视图解析器,解析得到一个View对象,这个View对象持有的就是完整的视图路径,前面viewname持有的是逻辑视图路径。
2.7开始渲染目标视图,由持有完整视图路径的View对象调用渲染方法,其中就传入了刚刚从modelandview对象中取出的模型数据model,这又对应了我们熟知的springmvc执行流程,不过不是视图解析器将model交给View这么人性化,这两个动作是独立的,视图解析器在这个流程中只返回了View对象。

view.render(mv.getModelInternal,request,response)

可以看到,这里渲染视图需要的model数据是直接从传入方法的ModelAndView对象中取得。
2.7.1将model数据转移到mergemodel中,其实也是个map,这个map专门用于存放渲染视图需要的各种数据。
2.7.2遍历mergemodel,循环调用request.setAttributes(),将map中的每个数据放到请求域中。
2.8.前面的所有步骤出现任何异常,都会有任何异常都会直接倒序触发handle对应的拦截器链的 已经执行了的拦截器的 afterCompletion方法
2.9视图正常渲染完成后,最终也会倒序触发handle对应的拦截器链的已经执行了的拦截器的 afterCompletion方法
3.0拦截器流程图:
在这里插入图片描述

三。请求参数

1.@pathvariable,获取请求路径中指定位置的值:
@GetMapping("/car/{id}/owner/{username}")
@PathVariable(“id”) Integer id,
获取{id}位置的值
2.@requestheader,获取请求头中指定属性的值:
@RequestHeader(“User-Agent”) String userAgent,
获取请求头中浏览器的信息
3.Spring中@RequestBody注解,用于读取Request请求的body数据,并根据内容类型(Content Type)确定使用哪个HttpMessageConverter进行解析。
3.1 application/x-www-form-urlencoded
提交的数据按照 key1=val1&key2=val2 的方式进行编码,大多数的数据提交为这种方式,包括JQuery的ajax等。在Spring中可以使用@RequestParam,@RequestBody,@ModelAttribute处理。
3.2 multipart/form-data
常见的上传文件表单提交方式。可使用@RequestParam注解处理。@RequestBody不能处理这种类型的数据。
3.3 application/json
以json格式提交数据。可以使用@RequestParam,@RequestBody处理接收数据。@requestbody一般也是用来处理json数据
4.矩阵变量:矩阵变量可以出现在任何路径片段中,每一个矩阵变量都用分号(;)隔开。比如 “/color=red;year=2012”。多个值可以用逗号隔开,比如 “color=red,green,blue”,或者分开写 “color=red;color=green;color=blue”。第一个分号之前是真正的路径变量,分号后面是矩阵变量。
4.1用法之一:cokkie被禁用时,session因为无法将jsessionid保存在cokkie中,可能无法使用,这个时候就可以使用矩阵变量来传递jsessionid,每次发请求都携带即可,这种解决办法也叫url重写。比如:
/abc;jsessionid=xxxx。
4.2springboot中使用@MatrixVariable注解来接受矩阵变量,与@pathvariable使用很相似,可以说矩阵变量是建立在路径变量的基础之上的,在路径变量的后面以分号间隔来传递多个值,总的来说,一个路径片段里包含的就是路径变量+多个矩阵变量:

//GET /books/42;a=11/author/21;a=22
@RequestMapping(value="/books/{bookId}/authors/{authorId}", method=RequestMethod.GET)
public void findBook(
    @MatrixVariabIe(value="a", pathVar="bookId") int q1,
    @MatrixVariabIe(value="a", pathVar="authorId) int q2){
        //q1 == 11
        //q2 == 22
}

可以用pathvar指定路径中指定片段,value指定获取其中的哪个值。这个时候,不同路径片段中就可以有同名的变量。
如果请求路径中只有一个占位符,可以不指定pathvar,只用指定value即可,同一路径片段中如果有多个同名值以分号间隔,需要使用集合接收:

//GET /books/22;a=12;b=22
@RequestMapping(value="/books/{bookId)",method=RequestMethod.GET)
public void findBookId (@PathVariable String bookId,@MatrixVariable int a){
    ...
}

4.3springboot默认禁用矩阵变量,想要开启,可以这样写:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper=new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

自定义urlpathhelper,并且设置忽略请求路径中分号的功能为false即可。

猜你喜欢

转载自blog.csdn.net/qq_44727091/article/details/113105202