SpringBoot-Web开发-内容协商——多端内容适配内容协商原理HttpMessageConverter
其它篇章:
一:SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割
二:SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&Favicon&自定义
一、多端内容适配
一套系统适配多端数据返回。
假设:
- 主体:写好一个接口
GET /person
。 - 部署:部署到服务器,暴露给外界访问。
- 外界:
1. 一个移动端应用:希望接口返回一个JSON文件;
2. 一个第三方应用:希望接口返回一个XML文件;
3. 一个IoT(物联网设备):希望接口返回一个自定义协议数据文件。
提问:
- 以以上假设为例,如何让一个接口做到多端的数据反馈?
思考:
- 一,可以写三个请求路径,分别为
/person.json
、/person.xml
、/person.diy(应用指定后缀)
,这三个不同应用分别访问指定的请求路径:- 缺点:这种方法只能用在内部接口少的情况下。如果,内部接口很多,那么每个接口都得拆分成三个接口,对应三个外部应用。若需要返回更多种类型数据文件,这个数量还得上升。
- 二,
SpringBoot
可以解决这个问题,详细看接下来的内容。
默认规则
a.主要内容
内容协商功能是SpringMVC自带的功能,SpringBoot对其进行了整合,快速实现一套系统适配多端数据返回。
SpringBoot多端内容适配
- 基于请求头内容协商(默认开启)
- 客户端 向 服务端 发送请求时,携带HTTP标准的Accept请求头。
- 客户端:外界应用
- 服务端:接口
accept
:是http协议里规定的一个标准
- 在 客户端 向 服务端 发送请求时,带上Accept,让 服务端 知道该返回什么类型的数据,SpringBoot 就把数据自动格式化成对应类型,再返回。
- 举例:
- Accept:
application/json
- Accept:
text\xml
- Accept:
text\yaml
- Accept:
- 举例:
- 基于请求参数内容协商(需要手动开启)
- 举例:
?format=json
:- 发送请求:
GET /projects/spring-boot?format=json
- 匹配:匹配到
GetMapping("/projects/spring-boot")
- 返回:根据参数协商,优先返回
json
类型数据
- 发送请求:
- 同理,发送请求
GET /projects/spring-boot?format=xml
,则优先返回xml
类型数据
b.演示验证
- 创建一个接口,其内容如下:
- 先绑定地址:
@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传输。
-
启动项目,在浏览器中验证:
- 浏览器中访问:
http://localhost:8080/person
- 发现数据的类型为
json
,如下图所示:
- 浏览器中访问:
-
思考:要想让数据适配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{(省略……)}
- 引入支持xml内容依赖:
- 开启基于请求参数的内容协商(默认关闭)
- 开启基于请求参数的内容协商功能,默认参数名为format
spring.mvc.contentnegotiation.favor-parameter=true
- 指定内容协商时使用的参数名,默认为format
- 在application.propertise中添加以下内容:
- 开启基于请求参数的内容协商功能,默认参数名为format
-
验证:
- 在浏览器中访问
http://localhost:8080/person?format=xml
- 在浏览器中访问
http://localhost:8080/person?type=json
- 在浏览器中访问
二、内容协商原理-HttpMessageConverter
学习逻辑链:
- 目的:想要自定义接口的返回内容
- 分析:要想自定义接口的返回内容,就要先理解内容协商的底层原理;而要理解内容协商的底层原理,只要知道
HttpMessageConverter
怎么工作,什么时候工作就行了;知道HttpMessageConverter
工作原理后,就能通过定制HttpMessageConverter
来实现多端内容协商,以达到自定义接口返回内容的目的。- 步骤:
a. 了解HttpMessageConverter
怎么工作,什么时候工作。
b. 这样就能,理解内容协商的底层原理。
c. 接下来就能,通过定制HttpMessageConverter
来实现多端内容协商。
d. 这样就,达到了想要自定义接口的返回内容的目的。- 总结:要想知道怎么自定义接口的返回内容,其实就是要知道怎么通过定制
HttpMessageConverter
来实现多端内容协商。
在WebMvcConfigurer
接口里,能够配置很多底层的东西,其中就包含了一个configureMessageConverters
,如下图。
只需要编写WebMvcConfigurer
接口提供的configureMessageConverters
底层,修改底层的MessageConverters
就可以了。
思考:只要修改以上的MessageConverters
就可以了,但为什么这样做就可以了?这就是接下来要说的内容了。
1、@ResponseBody由HttpMessageConverter处理
a.主要内容
在向浏览器返回内容的controller里,有一个
@RestController
注解,而这个注解里又包含一个@ResponseBody
,而注解标注在类上面,就表示标注在每一个方法上。
- 如果controller方法的返回值标注了
@ResponseBody
注解,
按照SpringMVC
的原理,我们从DispatcherServlet.class
开始,所有请求来到DispatcherServlet
,都是从doDispatch()
开始的。
ctlr+n搜索DispatcherServlet.class
,进入后继续ctlr+n搜索doDispatch()
,在其内容第一行设置一个断点,找到以下界面:
往下翻找到HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
。
- 如果根据/person这个请求路径找到了某个方法要处理,那就要找到这个适配器,最终利用这个适配器处理方法。往下翻找到
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
,如图。这是正真执行handler
的语句。
- 给person()第一行打个断点,并以debug启动,看看是怎么处理的。
浏览器访问http://localhost:8080/person?format=json
,先来到doDispatch
,它来接第一个请求。
放行。
再放行,来到正真执行handler
的语句的位置。
- 这个时候点击
step into
,进到这个目标执行方法,来到以下界面:
再点进handleInternal
,往下翻,看到invokeHandlerMethod
方法。
点进去,在第一行设置一个断点:
一直放行到这,目标方法还没进行到,只进行到invokeHandlerMethod
。
在RequestMappingHandlerAdapter.class
中往下找到invocableMethod.invokeAndHandle
,意思是执行并处理,把这一放行,就会去到person()
并执行。
这样就会得到结论:
RequestMappingHandlerAdapter.class
里面的invokeAndHandle()才是正真执行目标方法的。
b. 总结:
- 如果
controller
方法的返回值标注了@ResponseBody
注解:- 请求进来先来到
DispatcherServlet
的doDispatch()
进行处理 - 找到一个
HandlerAdapter
适配器,利用适配器执行目标方法 RequestMappingHandlerAdapter
来执行,调用invokeHandlerMethod()
来执行目标方法- 目标方法执行之前,准备好两个东西:
HandlerMethodArgumentResolver
:参数解析器,确定目标方法每个参数值HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值该怎么处理。
- 目标方法执行完成,会返回返回值对象
- 找到一个合适的返回值处理器
- 最终找到
RequestResponseBodyMethodProcessor
中能处理标注了@ResponseBody注解
的方法 RequestResponseBodyMethodReturnValueHandler
调用writeWithMessageConverters
,利用MessageConverter把返回值写出去。
- 请求进来先来到
看到
@GetMapping
,要知道这个注解都是由invokeHandlerMethod
所在的RequestMappingHandlerAdapter.class
其它篇章:
一:SpringBoot3-日志——日志原理&日志格式&日志级别&日志分组&文件输出&文件归档&滚动切割
二:SpringBoot3-Web开发-静态资源——WebMvcAutoConfiguration原理&资源映射&资源缓存&欢迎页&Favicon&自定义