• SpringBoot-Web开发-请求映射与请求参数处理


    目录

    一、请求映射

    1.1 Rest风格

    1.2 Rest风格的原理

    1.3 请求映射的原理

    二、参数处理

    2.1 普通参数注解

    2.2 @MatrixVariable注解

    2.3 ServletAPI 类型的参数

    2.4 复杂参数

    2.5 自定义对象参数

    三、参数处理原理

    3.1 执行目标方法

    3.2 参数解析器-HandlerMethodArgumentResolver

    3.3 返回值处理器 HandlerMethodReturnValueHandler

    3.4 自定义类型参数封装POJO

    3.5 目标方法执行完成


    一、请求映射

    1.1 Rest风格

            @RequestMapping注解可以用下面几个注解代替

    @Getmapping    @PostMapping    @DeleteMapping    @PutMapping

            分别代表 获取、保存、删除、修改的请求类型。url采用路径式的风格。

             使用RestFul风格提交请求时,要先在配置文件开启Rest功能,然后表单要加上一个隐藏域,表明请求方式。

            application.yml:

    1. spring:
    2. mvc:
    3. hiddenmethod:
    4. filter:
    5. enabled: true #开启页面表单的Rest功能

            index.html:

    1. <div>
    2. <form action="/user" method="get">
    3. <input value="Rest-GET" type="submit">
    4. form>
    5. <form action="/user" method="post">
    6. <input value="Rest-POST" type="submit">
    7. form>
    8. <form action="/user" method="post">
    9. <input type="hidden" name="_method" value="put">
    10. <input value="Rest-PUT" type="submit">
    11. form>
    12. <form action="/user" method="post">
    13. <input type="hidden" name="_method" value="delete">
    14. <input value="Rest-DELETE" type="submit">
    15. form>
    16. div>

            controller:

    1. @RestController
    2. public class testRESTful {
    3. @GetMapping("/user")
    4. public String testGet(){
    5. return "get user";
    6. }
    7. @PostMapping("/user")
    8. public String testPost(){
    9. return "post user";
    10. }
    11. @PutMapping("/user")
    12. public String testPut(){
    13. return "put user";
    14. }
    15. @DeleteMapping("/user")
    16. public String testDelete(){
    17. return "delete user";
    18. }
    19. }

    1.2 Rest风格的原理

    Rest原理(表单提交要使用REST的时候)

    • 表单提交会带上隐藏域的参数:_method=PUT/DELETE
    • 请求过来被HiddenHttpMethodFilter拦截
    • 判断表单请求类型是否是POST,只有是POST才是REST风格
    • 获取_method的参数,判断是PUT还是DELETE、PATCH
    • 过滤器HttpMethodRequestWrapper重写了getMethod方法,封装_method的值,这里用到了包装模式,返回的是我们想要的请求类型

    • 过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用HttpMethodRequestWrapper的,就是包装后的,而不是原生的。这样就可以使用PUTDELTETE请求啦。

    1.3 请求映射的原理

             doGet(HttpServlet)->processRequest(HttpServletBean)->

            doService(FramworkServlet)->doDispatch(DispatchServlet)

    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    2. HttpServletRequest processedRequest = request;
    3. HandlerExecutionChain mappedHandler = null;
    4. boolean multipartRequestParsed = false;
    5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    6. try {
    7. ModelAndView mv = null;
    8. Exception dispatchException = null;
    9. try {
    10. processedRequest = checkMultipart(request);
    11. multipartRequestParsed = (processedRequest != request);
    12. // 找到当前请求使用哪个Handler(Controller的方法)处理
    13. mappedHandler = getHandler(processedRequest);
    14. ... ...
    • 所有请求都会调用DispatchServletdoDispatch方法,在该方法中找到当前请求使用哪个Handler(Controller中的方法)进行处理。
    • 如何找呢?通过handlerMapping(处理器映射)

    • 总共有五个handlerMapping,其中第一个保存了所有@RequestMapping 注解和handler映射规则,就是我们用户标注的那些控制器方法。SpringMVC启动时将所有@RequestMapping 注解信息保存到这个handlerMappingmappingRegistry中。
    • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
      • 如果有就找到这个请求对应的handler
      • 如果没有就是下一个 HandlerMapping

    二、参数处理

    2.1 普通参数注解

    @PathVariable        Rest风格路径动态的参数映射到处理器方法的形参

    @RequestHeader   请求头信息和控制器方法形参创建映射关系

    @ModelAttribute

    @RequestParam    请求参数控制器方法形参创建映射关系

    @MatrixVariable      获取矩阵变量

    @CookieValue         cookie数据控制器方法形参创建映射关系

    @RequestBody        获取请求体

    @ResponseBody        标注方法返回值作为响应体

    @RequestAttritube        获取request域属性(页面跳转时取域数据)

    <a href="/car/1/owner/wz">@PathVariablea>
    1. @RestController
    2. public class testParam {
    3. @GetMapping("/car/{id}/owner/{username}")
    4. public Map getCar(@PathVariable("id") Integer id,@PathVariable Map vars){
    5. Map map=new HashMap<>();
    6. map.put("id",id);
    7. map.put("vars",vars);
    8. return map;
    9. }
    10. }
    <a href="/car/1/owner/wz">@RequestHeadera>
    1. @RestController
    2. public class testParam {
    3. @GetMapping("/car/{id}/owner/{username}")
    4. public Map getCar(@RequestHeader Map headers,@RequestHeader("User-Agent") String header){
    5. Map map=new HashMap<>();
    6. map.put("header",header);//某个key 对应的 value
    7. map.put("headers",headers);//所有key 对应的 value
    8. return map;
    9. }
    10. }
    11. 结果:
    12. {"headers":{"host":"localhost:8080","connection":"keep-alive","sec-ch-ua":"\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"103\", \"Chromium\";v=\"103\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","sec-fetch-site":"same-origin","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","referer":"http://localhost:8080/index.html","accept-encoding":"gzip, deflate, br","accept-language":"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6","cookie":"Idea-78910da9=e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"},"header":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77"}
    <a href="/car/1/owner/ikun?age=18&interest=rap">@RequestParama>
    1. @RestController
    2. public class testParam {
    3. @GetMapping("/car/{id}/owner/{username}")
    4. public Map getCar(@RequestParam("age") Integer age,@RequestParam Map params){
    5. Map map=new HashMap<>();
    6. map.put("age",age);
    7. map.put("params",params);
    8. return map;
    9. }
    10. }
    11. 结果:
    12. {"params":{"age":"18","interest":"rap"},"age":18}
    <a href="/car/1/owner/ikun?age=18&interest=rap">@CookieValuea>
    1. @RestController
    2. public class testParam {
    3. @GetMapping("/car/{id}/owner/{username}")
    4. public Map getCar(@CookieValue("Idea-78910da9") String c){
    5. Map map=new HashMap<>();
    6. map.put("Idea-78910da9",c);
    7. return map;
    8. }
    9. }
    10. 结果:
    11. {"Idea-78910da9":"e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"}
    1. <form action="/car" method="post">
    2. 汽车品牌:<input name="brand"/>
    3. 汽车型号:<input name="model"/>
    4. <input type="submit" value="提交">
    5. form>
    1. @RestController
    2. public class testParam {
    3. @PostMapping("/car")
    4. public Map getCar(@RequestBody String body){
    5. Map map=new HashMap<>();
    6. map.put("RequestBody",body);
    7. return map;
    8. }
    9. }
    10. 结果:
    11. {"RequestBody":"brand=Toyota & model=Camary"}

    2.2 @MatrixVariable注解


    矩阵变量请求是一种新的请求风格,严格来说矩阵变量的请求需要用到rest风格但是又不同于rest.

    1. //queryString请求方式
    2. /cars?brand=audi&model=A6&price=500000
    3. //REST请求
    4. /cars/audi/A6/500000
    5. //MatrixVariable矩阵变量
    6. /cars;brand=audi;model=A6;price=500000

            这个功能默认关闭的,要在配置类中手动开启:

    1. @Configuration
    2. public class myConfig implements WebMvcConfigurer {
    3. @Override
    4. public void configurePathMatch(PathMatchConfigurer configurer) {
    5. UrlPathHelper urlPathHelper = new UrlPathHelper();
    6. urlPathHelper.setRemoveSemicolonContent(false);//矩阵变量功能生效
    7. configurer.setUrlPathHelper(urlPathHelper);
    8. }
    9. }

            下面是遇到一个path时的情况: 

    <a href="/cars/sell;brand=audi;model=A6;price=500000,4000000">@MatrixVariablea>
    1. @GetMapping("/cars/{path}")
    2. public Map carsSell(@MatrixVariable("brand") String brand,
    3. @MatrixVariable("model") String model,
    4. @MatrixVariable("price") List price,
    5. @PathVariable("path") String path){
    6. Map map = new HashMap<>();
    7. map.put("brand",brand);
    8. map.put("model",model);
    9. map.put("price",price);
    10. map.put("path",path);
    11. return map;
    12. }
    13. 结果:
    14. {"path":"sell","price":["500000","4000000"],"model":"A6","brand":"audi"}

            下面是遇到多个path时的情况:

    <a href="/cars/1;brand=audi/2;brand=benz">@MatrixVariablea>
    1. @GetMapping("/cars/{id1}/{id2}")
    2. public Map carBrand(@MatrixVariable(value = "brand", pathVar = "id1") String id1brand,
    3. @MatrixVariable(value = "brand", pathVar = "id2") String id2brand){
    4. Map map = new HashMap<>();
    5. map.put("id1brand",id1brand);
    6. map.put("id2brand",id2brand);
    7. return map;
    8. }
    9. 结果:
    10. {"id1brand":"audi","id2brand":"benz"}

    2.3 ServletAPI 类型的参数

    WebRequestServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

            如何获取到这些参数呢?

            ServletRequestMethodArgumentResolver 帮我们解析以上部分参数的。

    2.4 复杂参数

            如MapModel(map、model里面的数据会被放在request的请求域)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

             Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMapModeMap类型的,最终都会在DispatcherServlet封装成一个ModelAndView

            如何使用已经在SpringMVC中介绍了。

    2.5 自定义对象参数

            就是自动将表单提交的内容与javaBean的属性相对应。可以自动类型转换与格式化,可以级联封装。

    1. /**
    2. * 姓名: <input name="userName"/> <br/>
    3. * 年龄: <input name="age"/> <br/>
    4. * 生日: <input name="birth"/> <br/>
    5. * 宠物姓名:<input name="pet.name"/><br/>
    6. * 宠物年龄:<input name="pet.age"/>
    7. */
    1. @Data
    2. public class Person {
    3. private String userName;
    4. private Integer age;
    5. private Date birth;
    6. private Pet pet;
    7. }
    8. @Data
    9. public class Pet {
    10. private String name;
    11. private String age;
    12. }

    三、参数处理原理

    • HandlerMapping中找到能处理请求的Handler(Controller.method())
    • 为当前Handler 找一个适配器 HandlerAdapterRequestMappingHandlerAdapter
    • 适配器执行目标方法并确定方法参数的每一个值

    3.1 执行目标方法

    1. // Actually invoke the handler.
    2. //DispatcherServlet -- doDispatch
    3. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    4. mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
    5. //ServletInvocableHandlerMethod
    6. Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    7. //获取方法的参数值
    8. Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

    3.2 参数解析器-HandlerMethodArgumentResolver

            确定将要执行的目标方法的每一个参数的值是什么;SpringMVC目标方法能写多少种参数类型。取决于参数解析器

             解析器挨个判断是否支持解析这种参数,如果支持就调用 resolveArgument 方法解析

    1. @Nullable
    2. private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    3. HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    4. if (result == null) {
    5. for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    6. if (resolver.supportsParameter(parameter)) {
    7. result = resolver;
    8. this.argumentResolverCache.put(parameter, result);
    9. break;
    10. }
    11. }
    12. }
    13. return result;
    14. }

    3.3 返回值处理器 HandlerMethodReturnValueHandler

            这个处理器会根据返回值类型调用对应的处理类进行处理。 

    返回值类型处理类
    String(方法上无ResponseBody注解)ViewNameMethodReturnValueHandler
    ViewViewMethodReturnValueHandler
    ModelAndViewModelAndViewMethodReturnValueHandle
    ModelModelMethodProcessor
    MapMapMethodProcessor
    HttpHeadersHttpHeadersReturnValueHandler
    ModelAttribute(或者是自定义对象)ModelAttributeMethodProcessor
    HttpEntityHttpEntityMethodProcessor
    ResponseEntityResponseBodyEmitterReturnValueHandler
    ResponseBody注解RequestResponseBodyMethodProcessor

    3.4 自定义类型参数封装POJO

            由ServletModelAttributeMethodProcessor 参数处理器处理,它支持自定义参数绑定,在底层使用了WebDataBinder数据绑定器,而数据绑定器中有一个conversionServiceconversionService注册了很多convertersconverters会帮助我们进行类型转换

    3.5 目标方法执行完成

            将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View,还包含Model数据。接下来还是封装成 ModelAndView,最后处理派发结果数据放到请求域

  • 相关阅读:
    AM@分部积分原理和应用
    6、链表和数组,哪个实现队列更快?
    Linux 安装中央仓库 Nexus
    redis实现点赞功能。
    Redis源码(1) 建立监听服务和开启事件循环
    (247)Verilog HDL:串联T触发器
    An动画基础之元件的影片剪辑动画与传统补间
    呸 渣男!八股文不让看,非得让看并发编程全彩图册,这下又进厂了
    pycocotools库的使用
    Java程序中常用的设计模式有哪些和该种设计模式解决的痛点
  • 原文地址:https://blog.csdn.net/weixin_62427168/article/details/126124345