Spring Boot REST(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

SpringBoot REST 系列相关的文章:

  1. SpringBoot REST(一)核心接口
  2. SpringBoot REST(二)源码分析

在上一篇文章中提到了 Spring Boot 中的 REST 的一些使用方法,@ResponseBody 默认返回一个 json,如果需要返回 xml 或者自定义返回媒体类型时怎么办呢?

@GetMapping("/v1/{user_id}")
public User user(@PathVariable("user_id") String userId) {
return new User(userId, "binarylei", "123456");
}

一、自定义媒体类型

1.1 媒体类型

首先要解释媒体类型这个概念,常见的媒体类型有 application/json、application/xml 等。

(1) 浏览器

浏览器即可以指定要发送的格式(Content-Type),也可以指定可以要接收的数据格式(Accept),如下表示发送 json 格式,接收 xml 格式:

Content-Type: application/xml
Accept: application/json

(2) 服务器

@RequestMapping 注解有两个参数可以匹配这种请求。下面这个只处理发送的请求是 xml 格式,返回 json 的请求。

@GetMapping(value = "/v3/xml/to/json",
consumes = "application/xml",
produces = "application/json")
public User propertiesToHJson(@RequestBody User user) {
return new User("1", "com/github/binarylei", "123456");
}

1.2 引入 application/xml 解析器

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Spring Boot 默认只能处理 json 这种媒体类型,引入上述的 jar 包后就可以处理 xml 格式了。

1.3 自定义解析器

(1) PropertiesHttpMessageConverter

public class PropertiesHttpMessageConverter extends AbstractHttpMessageConverter<User> {
public PropertiesHttpMessageConverter() {
super(Charset.forName("utf-8"), MediaType.valueOf("application/properties"));
} @Override
protected boolean supports(Class clazz) {
return clazz == User.class;
} @Override
protected User readInternal(Class<? extends User> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
Properties properties = new Properties();
properties.load(inputMessage.getBody());
User user = new User();
user.setUserId(properties.getProperty("user.id"));
user.setUsername(properties.getProperty("user.name"));
return user;
} @Override
protected void writeInternal(User user, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
Properties properties = new Properties();
properties.setProperty("user.id", user.getUserId());
properties.setProperty("user.name", user.getUsername());
properties.setProperty("user.password", user.getPassword());
properties.store(outputMessage.getBody(), "write");
}
}

(2) 配置类 PropertiesWebMvcConfigurer

@Configuration
public class PropertiesWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new PropertiesHttpMessageConverter());
}
}

(3) rest 接口定义

@GetMapping(value = "/v3/properties/to/json",
consumes = "application/properties",
produces = "application/json")
public User propertiesToHJson(@RequestBody User user) {
return new User("1", "binarylei", "123456");
} @GetMapping(value = "/v3/json/to/properties",
consumes = "application/json",
produces = "application/properties")
public User jsonToProperties(@RequestBody User user) {
return new User("1", "binarylei", "123456");
}

(4) 测试

  1. 测试1:
请求地址:localhost:8080//v3/properties/to/json
请求头:Accept: application/properties, Content-Type: application/json
请求参数:user.id=1 user.name=binarylei
  1. 测试2:
请求地址:localhost:8080/v3/json/to/properties
请求头:Accept: application/json, Content-Type: application/properties
请求参数:{}

二、源码分析

@EnableWebMvc 注入了 DelegatingWebMvcConfiguration 组件,其类图结构如下:

2.1 默认 HttpMessageConverter 加载

在 WebMvcConfigurationSupport 类中定义了许多默认的 HttpMessageConverter,根据是否有相应的类加载来判断是否启动对应的 HttpMessageConverter。

// 类型转换器
private List<HttpMessageConverter<?>> messageConverters; protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters); // (1)
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters); // (2)
}
extendMessageConverters(this.messageConverters); // (3)
}
return this.messageConverters;
}

(1) 由子类 DelegatingWebMvcConfiguration 重写了 configureMessageConverters 方法,实际上是委托给了 WebMvcConfigurer 完成。

(2) 加载默认的 HttpMessageConverter

(3) 同 (1),也是由子类重写 extendMessageConverters

下面我们看一下 Spring Boot 默认加载了那些 HttpMessageConverter

boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
try {
messageConverters.add(new SourceHttpMessageConverter<>());
} catch (Throwable ex) {
// Ignore when no TransformerFactory implementation is available...
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter()); // 省略...
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
// 省略...
}

可以看到除了 ByteArrayHttpMessageConverter 等是固定加载外,其余的都是通过判断是否有相应的类来决定是否启用。如果需要使用相应的解析器,只需要到相应的 jar 包添加到 pom.xml 中即可。

最终容器中加载了如下的 HttpMessageConverter 解析器:

0 = {ByteArrayHttpMessageConverter@5783}
1 = {StringHttpMessageConverter@5784}
2 = {ResourceHttpMessageConverter@5785}
3 = {ResourceRegionHttpMessageConverter@5786}
4 = {SourceHttpMessageConverter@5787}
5 = {AllEncompassingFormHttpMessageConverter@5788}
6 = {MappingJackson2XmlHttpMessageConverter@5789}
7 = {MappingJackson2HttpMessageConverter@5790}

2.2 HttpMessageConverter 执行过程

上文中提到 Spring Boot 启动时会在 messageConverters 集合中加载多个 HttpMessageConverter,到底执行那个呢?毫无疑问,执行肯定有三个过程:一是匹配对应的 HttpMessageConverter;二是执行 Handler;三是执行 HttpMessageConverter 响应结果。

HttpMessageConverter 的执行是在 AbstractMessageConverterMethodProcessor#writeWithMessageConverters 中执行的,这个方法很长,我们一点点来看。

2.2.1 匹配 HttpMessageConverter

MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
// 1. 获取客户端可接受的类型 Accept: application/jsion
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 2. 服务端可以生成的所有 MediaType 类型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
} // 3. acceptableTypes 和 producibleTypes 比较,找出可用的 MediaType
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
return;
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse); // 4. 如果有多个 MediaType 可用,选择一个可用的返回
for (MediaType mediaType : mediaTypesToUse) {
// 只要是非 */* 就直接返回
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
} else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
}

客户端可以传两个请求头过来:

Accept: application/xml         // 客户端可接收的媒体类型
Content-Type: application/json // 客户端请求的媒体类型

2.2.2 执行 HttpMessageConverter

// 遍历 messageConverters,如果 converter 支持 selectedMediaType 则直接返回
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
// 1. canWrite 返回 true 则直接执行并结束循环
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) { // 2. 拿到 handler 的执行结果
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
addContentDispositionHeader(inputMessage, outputMessage); // 3. 执行对应的 genericConverter
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
} else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
return;
}
}

核心的步骤 converter.write(body, selectedMediaType, outputMessage) 将 POJO 转换为 json 或 xml 后返回。

2.2.3 HttpMessageConverter

如果需要自定义 HttpMessageConverter,可以直接继承 AbstractHttpMessageConverter 类,重写 supports、readInternal、writeInternal 方法。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Spring Boot REST(二)源码分析的更多相关文章

  1. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  2. spring boot 2.0 源码分析(一)

    在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...

  3. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  4. spring boot 2.0 源码分析(二)

    在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...

  5. spring boot 2.0 源码分析(三)

    通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分 ...

  6. spring boot 2.0 源码分析(五)

    在上一篇文章中我们详细分析了spring boot是如何准备上下文环境的,今天我们来看一下run函数剩余的内容.还是先把run函数贴出来: /** * Run the Spring applicati ...

  7. Spring中Bean命名源码分析

    Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...

  8. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  9. Spring JPA实现逻辑源码分析总结

    1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...

  10. 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)

    1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e,  要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...

随机推荐

  1. ABAP IMPORT&EXPORT的用法

    1含有事务码 1.1 不注入参数,直接调用 CALL TRANSACTION 'SUIM' AND SKIP FIRST SCREEN. 1.2 注入参数, SET PARAMETER ID: '屏幕 ...

  2. requests库的文档--快速上手

    快速上手 迫不及待了吗?本页内容为如何入门 Requests 提供了很好的指引.其假设你已经安装了 Requests.如果还没有,去安装一节看看吧. 首先,确认一下: Requests 已安装 Req ...

  3. 大型运输行业实战_day14_1_webserivce简单入门

    1.简单使用 1.1.服务端 1.编写接口 package com.day02.sation.ws; /** * Created by Administrator on 1/12. */ public ...

  4. iostat磁盘监控工具

    安装iostat磁盘监控工具 1.安装 yum install sysstat 2.运行 iostat -k -d -x 1 10 -k:以kb为单位统计 -d:显示磁盘状态 -x:显示详细信息 1: ...

  5. 根据23423条件,截取字段‘abdecsdsadsadsad’,以ab/dec/sdsa/ds/ads 输出

    create or replace procedure p_getString ( p_finalString out varchar2, p_rulestring in number, p_sour ...

  6. 【Django】关于scss 的安装

    今天看视频教程的时候发现老师的样式文件改用了scss(然鹅我买的1块钱特价课程其实是节选出来的,所以前面没有看到过关于scss的介绍) 然后我本以为把原来的css改名字为scss就行,然鹅没有效果. ...

  7. java面试题:Spring

    Spring 面试时,最好能结合底层代码说出IOC,AOP或Spring MVC的流程,能说出拦截器的底层. 如果看过Spring的源码,并能结合设计模式表达,是很大的加分项. IOC Q:讲一下IO ...

  8. 六:python 对象类型详解二:字符串(下)

    一:字符串方法: 方法就是与特定对象相关联在一起的函数.从技术的角度来讲,它们是附属于对象的属性,而这些属性不过是些可调用的函数罢了.Python 首先读取对象方法,然后调用它,传递参数.如果一个方法 ...

  9. 最长公共子序列lcs 51nod1006

    推荐参考博客:动态规划基础篇之最长公共子序列问题 - CSDN博客  https://blog.csdn.net/lz161530245/article/details/76943991 个人觉得上面 ...

  10. 662. Maximum Width of Binary Tree二叉树的最大宽度

    [抄题]: Given a binary tree, write a function to get the maximum width of the given tree. The width of ...