其它篇章:
一:SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割
二:SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&Favicon&自定义

一、多端内容适配

一套系统适配多端数据返回。

假设:
  • 主体:写好一个接口GET /person
  • 部署:部署到服务器,暴露给外界访问。
  • 外界
    1. 一个移动端应用:希望接口返回一个JSON文件;
    2. 一个第三方应用:希望接口返回一个XML文件;
    3. 一个IoT(物联网设备):希望接口返回一个自定义协议数据文件。
提问:
  • 以以上假设为例,如何让一个接口做到多端的数据反馈?
思考:
  • ,可以写三个请求路径,分别为/person.json/person.xml/person.diy(应用指定后缀),这三个不同应用分别访问指定的请求路径:
    • 缺点:这种方法只能用在内部接口少的情况下。如果,内部接口很多,那么每个接口都得拆分成三个接口,对应三个外部应用。若需要返回更多种类型数据文件,这个数量还得上升。
  • SpringBoot可以解决这个问题,详细看接下来的内容。

默认规则

a.主要内容

内容协商功能是SpringMVC自带的功能,SpringBoot对其进行了整合,快速实现一套系统适配多端数据返回。

SpringBoot多端内容适配
  1. 基于请求头内容协商(默认开启)
  • 客户端服务端 发送请求时,携带HTTP标准的Accept请求头。
    • 客户端:外界应用
    • 服务端:接口
    • accept:是http协议里规定的一个标准
  • 客户端服务端 发送请求时,带上Accept,让 服务端 知道该返回什么类型的数据,SpringBoot 就把数据自动格式化成对应类型,再返回。
    • 举例
      • Accept:application/json
      • Accept:text\xml
      • Accept:text\yaml
  1. 基于请求参数内容协商(需要手动开启)
  • 举例
    • ?format=json:
      • 发送请求: GET /projects/spring-boot?format=json
      • 匹配:匹配到 GetMapping("/projects/spring-boot")
      • 返回:根据参数协商,优先返回json类型数据
    • 同理,发送请求 GET /projects/spring-boot?format=xml,则优先返回xml类型数据

b.演示验证

  1. 创建一个接口,其内容如下:
    • 先绑定地址:@GetMapping("/person")
    • 再定义一个 Person类。
    • 创建的接口代码如下:
      package com.atwyb.web.controller;
      
      import com.atwyb.web.bean.Person;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class HelloController {
          @GetMapping("/person")
          public Person person(){
              Person person = new Person();
              person.setId("1");
              person.setUserName("zhangsan");
              return person;
          }
      }
      
    • person的javabean如下:
      package com.atwyb.web.bean;
      
      public class Person {
          private String id;
          private String userName;
      
          public String getId() {
              return id;
          }
      
          public void setId(String id) {
              this.id = id;
          }
      
          public String getUserName() {
              return userName;
          }
      
          public void setUserName(String userName) {
              this.userName = userName;
          }
      }
      

点进@RestController,发现标注了@ResponseBody。说明@RestController返回的数据都标注了@ResponseBody,而所有标注了@ResponseBody的对象,都会以json传输。
在这里插入图片描述

  1. 启动项目,在浏览器中验证:

    • 浏览器中访问:http://localhost:8080/person
    • 发现数据的类型为json,如下图所示:在这里插入图片描述
  2. 思考:要想让数据适配xml,该怎么做?

    • SpringBoot默认支持把对象写为json。因为默认web场景导入了jackson处理json的包:jackson-core。
    • jackson是一个库,也支持把数据写为xml,但要导入xml相关依赖:
      • 引入支持xml内容依赖:
         <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
        
      • 标注xml注解:在Person这个JavaBean下标注xml注解
        @JacksonXmlRootElement
        public class person{(省略……)}
        
    • 开启基于请求参数的内容协商(默认关闭)
      • 开启基于请求参数的内容协商功能,默认参数名为format
        spring.mvc.contentnegotiation.favor-parameter=true
        
      • 指定内容协商时使用的参数名,默认为format
      • 在application.propertise中添加以下内容:
        在这里插入图片描述
  3. 验证:

    • 在浏览器中访问http://localhost:8080/person?format=xml
      在这里插入图片描述
    • 在浏览器中访问http://localhost:8080/person?type=json在这里插入图片描述

二、内容协商原理-HttpMessageConverter

学习逻辑链:

  1. 目的:想要自定义接口的返回内容
  2. 分析:要想自定义接口的返回内容,就要先理解内容协商的底层原理;而要理解内容协商的底层原理,只要知道HttpMessageConverter怎么工作,什么时候工作就行了;知道HttpMessageConverter工作原理后,就能通过定制HttpMessageConverter来实现多端内容协商,以达到自定义接口返回内容的目的。
  3. 步骤:
    a. 了解HttpMessageConverter怎么工作,什么时候工作。
    b. 这样就能,理解内容协商的底层原理
    c. 接下来就能,通过定制HttpMessageConverter来实现多端内容协商。
    d. 这样就,达到了想要自定义接口的返回内容的目的。
  4. 总结:要想知道怎么自定义接口的返回内容,其实就是要知道怎么通过定制HttpMessageConverter来实现多端内容协商。

WebMvcConfigurer接口里,能够配置很多底层的东西,其中就包含了一个configureMessageConverters,如下图。
在这里插入图片描述
只需要编写WebMvcConfigurer接口提供的configureMessageConverters底层,修改底层的MessageConverters就可以了。
思考:只要修改以上的MessageConverters就可以了,但为什么这样做就可以了?这就是接下来要说的内容了。

1、@ResponseBody由HttpMessageConverter处理

a.主要内容

在向浏览器返回内容的controller里,有一个@RestController注解,而这个注解里又包含一个@ResponseBody,而注解标注在类上面,就表示标注在每一个方法上。

  1. 如果controller方法的返回值标注了@ResponseBody注解,
    按照SpringMVC的原理,我们从DispatcherServlet.class开始,所有请求来到DispatcherServlet,都是从doDispatch()开始的。
    ctlr+n搜索DispatcherServlet.class,进入后继续ctlr+n搜索doDispatch(),在其内容第一行设置一个断点,找到以下界面:
    在这里插入图片描述
    往下翻找到HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    在这里插入图片描述
  2. 如果根据/person这个请求路径找到了某个方法要处理,那就要找到这个适配器,最终利用这个适配器处理方法。往下翻找到mv = ha.handle(processedRequest, response, mappedHandler.getHandler());,如图。这是正真执行handler的语句。
    在这里插入图片描述
  3. 给person()第一行打个断点,并以debug启动,看看是怎么处理的。
    在这里插入图片描述
    浏览器访问http://localhost:8080/person?format=json,先来到doDispatch,它来接第一个请求。在这里插入图片描述
    放行。
    在这里插入图片描述
    再放行,来到正真执行handler的语句的位置。
    在这里插入图片描述
  4. 这个时候点击step into,进到这个目标执行方法,来到以下界面:
    在这里插入图片描述
    再点进handleInternal,往下翻,看到invokeHandlerMethod方法。
    在这里插入图片描述
    点进去,在第一行设置一个断点:
    在这里插入图片描述
    一直放行到这,目标方法还没进行到,只进行到invokeHandlerMethod
    RequestMappingHandlerAdapter.class中往下找到invocableMethod.invokeAndHandle,意思是执行并处理,把这一放行,就会去到person()并执行。在这里插入图片描述
    这样就会得到结论:
    RequestMappingHandlerAdapter.class里面的invokeAndHandle()才是正真执行目标方法的。

b. 总结:

  • 如果controller方法的返回值标注了@ResponseBody注解:
    1. 请求进来先来到DispatcherServletdoDispatch()进行处理
    2. 找到一个HandlerAdapter适配器,利用适配器执行目标方法
    3. RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
    4. 目标方法执行之前,准备好两个东西:
      • HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
      • HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值该怎么处理。
    5. 目标方法执行完成,会返回返回值对象
    6. 找到一个合适的返回值处理器
    7. 最终找到RequestResponseBodyMethodProcessor中能处理 标注了@ResponseBody注解 的方法
    8. RequestResponseBodyMethodReturnValueHandler调用writeWithMessageConverters,利用MessageConverter把返回值写出去。

看到@GetMapping,要知道这个注解都是由invokeHandlerMethod所在的RequestMappingHandlerAdapter.class

其它篇章:
一:SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割
二:SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&Favicon&自定义

更多推荐