文章目录
本篇内容与POJO转JSON之原理解析有重复。
将Person类转换为JSON
首先新建spring项目:demo5,添加Spring Web、Lombok依赖,自动生成的pom.xml中包含如下依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
com.exmaple.boot包下新建类bean.Person。
package com.example.boot.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Person {
private String username;
private Integer age;
private Date birth;
}
com.example.boot包下新建控制器类controller.Demo5Controller。
package com.example.boot.controller;
import com.example.boot.bean.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
@Controller
public class Demo5Controller {
@GetMapping("/person")
@ResponseBody
public Person person(){
Person person = new Person("张三", 1, new Date());
return person;
}
}
resources.static下新建静态页面index.html。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<a href="/person">测试</a>
</body>
</html>
最后,启动应用,访问接口localhost:8080/person,返回的结果如下。
1. RequestResponseBodyMethodProcessor#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
2. AbstractMessageConverterMethodProcessor#writeWithMessageConverters
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//...
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
HttpServletRequest request = inputMessage.getServletRequest();
//...
List<MediaType> acceptableTypes;
try {
acceptableTypes = getAcceptableMediaTypes(request);
}
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
}
}
}
MediaType contentType = outputMessage.getHeaders().getContentType()
,判断当前响应是否已有确定的媒体类型。acceptableTypes = getAcceptableMediaTypes(request)
,读取浏览器请求字段Accept,从而获取浏览器支持接受哪些内容类型。
//AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}
//ContentNegotiationManager#resolveMediaTypes
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}
//HeaderContentNegotiationStrategy#resolveMediaTypes
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
//...
}
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType)
,统计出服务器能够生产的内容类型。
在getProducibleMediaTypes这个方法中,将通过for (HttpMessageConverter<?> converter : this.messageConverters)
遍历当前系统所有的消息转换器,找到能够操作Person类型的消息转换器,然后通过result.addAll(converter.getSupportedMediaTypes(valueClass))
将该消息转换器支持的所有媒体类型统计出来。
//oAbstractMessageConverterMethodProcessor#getProducibleMediaTypes
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes(valueClass));
}
}
//...
}
- 关于消息转换器
当前系统有10个消息转换器,如下。
这些消息转换器全部实现了接口类HttpMessageConverter,HttpMessageConverter定义如下,包含5个方法。
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
下面对以上10个消息转换器进行简单介绍。
//第一个消息转换器: ByteArrayHttpMessageConverter,仅支持操作byte[]类型。
public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<byte[]> {
@Override
public boolean supports(Class<?> clazz) {
return byte[].class == clazz;
}
}
//第二个消息转换器:StringHttpMessageConverter,仅支持操作String类型
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
@Override
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
}
//第三个消息转换器:StringHttpMessageConverter,仅支持操作String类型
//第四个消息转换器:ResourceHttpMessageConverter,仅支持操作Resource类型
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
@Override
protected boolean supports(Class<?> clazz) {
return Resource.class.isAssignableFrom(clazz);
}
}
//第五个消息转换器:ResourceRegionHttpMessageConverter,支持操作ResourceRegion类型
public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
public boolean canWrite(@Nullable Type type, @Nullable Class<?> clazz, @Nullable MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return (type instanceof Class && ResourceRegion.class.isAssignableFrom((Class<?>) type));
}
//...
}
}
//第六个消息转换器:SourceHttpMessageConverter,支持操作DOMSource、SAXSource、StAXSource、StreamSource和Source类型
public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMessageConverter<T> {
static {
SUPPORTED_CLASSES.add(DOMSource.class);
SUPPORTED_CLASSES.add(SAXSource.class);
SUPPORTED_CLASSES.add(StAXSource.class);
SUPPORTED_CLASSES.add(StreamSource.class);
SUPPORTED_CLASSES.add(Source.class);
}
@Override
public boolean supports(Class<?> clazz) {
return SUPPORTED_CLASSES.contains(clazz);
}
}
//第七个消息转换器:AllEncompassingFormHttpMessageConverter
public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {}
public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> {
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
if (!MultiValueMap.class.isAssignableFrom(clazz)) {
return false;
}
//...
}
}
//第八个消息转换器:MappingJackson2HttpMessageConverter
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {}
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
if (!canWrite(mediaType)) {
return false;
}
}
}
public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T>
implements GenericHttpMessageConverter<T> {
@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
return canWrite(clazz, mediaType);
}
}
//第九个消息转换器:MappingJackson2HttpMessageConverter
//第十个消息转换器:Jaxb2RootElementHttpMessageConverter
public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter<Object> {}
- 经过以上步骤后,已经知晓:浏览器端能够接受哪些内容类型,服务器能够生产哪些内容类型。接下来通过内容协商(嵌套for循环、排序、去重)找到最佳匹配的媒体类型。
//嵌套for循环
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
//排序
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
//去重
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
}
- 确定了最终的媒体类型后,接下来就是使用 支持将Person类型转换为指定媒体类型 的消息转换器进行转换,并将转换后的结果作为响应返回给浏览器。
3. AbstractGenericHttpMessageConverter#write
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
writeInternal(t, type, outputMessage);
outputMessage.getBody().flush();
}
4. AbstractJackson2HttpMessageConverter#writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
ObjectWriter objectWriter = (serializationView != null ?
objectMapper.writerWithView(serializationView) : objectMapper.writer());
objectWriter.writeValue(generator, value);
writeSuffix(generator, object);
generator.flush();
}
将Person类转换为XML
pom.xml中添加xml依赖,如下。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.13.1</version>
</dependency>
启动应用,访问接口:localhost:8080/person,结果如下。
调试一下,可以看到,当前系统中的消息转换器从刚刚的10个,增加到了11个。新增的消息转换器MappingJackson2XmlHttpMessageConverter
可以将实体类转换为XML。
返回json和xml
在demo5根目录下新建目录http,并在http目录下新建文件demo5.http
,内容如下,
GET localhost:8080/person
Accept: application/json
###
GET localhost:8080/person
Accept: application/xml
###
启动应用,访问接口。
- Accept: application/json 时,返回结果为
GET http://localhost:8080/person
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 06 Jan 2022 08:34:43 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"username": "张三",
"age": 1,
"birth": "2022-01-06T08:34:43.520+00:00"
}
- Accept: application/xml 时,返回结果为
GET http://localhost:8080/person
HTTP/1.1 200
Content-Type: application/xml;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 06 Jan 2022 08:31:58 GMT
Keep-Alive: timeout=60
Connection: keep-alive
<Person>
<username>张三</username>
<age>1</age>
<birth>2022-01-06T08:31:57.987+00:00</birth>
</Person>