POJO转JSON之原理解析

相似文章有:SpringBoot内置的jackson和lombok的使用HttpMessageConverter

首先,新建spring项目:demo4,且添加依赖:Spring Web和lombok。

spring-boot-starter-web>spring-boot-starter-json>jackson jar,依赖关系如下。
在这里插入图片描述
java目录下新建包com.example.boot,并在该包下新建类:bean.Person和bean.Pet,如下。

package com.example.boot.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}
package com.example.boot.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Pet {
    private String name;
    private Float weight;
}

com.example.boot下新建控制器类controller.Demo4Controller,如下。

package com.example.boot.controller;

import com.example.boot.bean.Person;
import com.example.boot.bean.Pet;
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 Demo4Controller {
    @GetMapping("/person")
    @ResponseBody
    private Person save(){
        Person person = new Person();
        person.setAge(22);
        person.setUserName("张三");
        person.setBirth(new Date());
        person.setPet(new Pet("小苹果",8.5f));
        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>

最后,启动应用,点击测试,查看结果。
在这里插入图片描述
也就是,jackson jar+@ResponseBody,将Person类转换成了JSON,最终返回给了响应。

接下来探寻下原理,调试过程经过了以下部分源码。

  1. org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//...
	// Determine handler adapter for the current request.
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
	//...
	// Actually invoke the handler.
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
	//...
}
  1. org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}
  1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	//...
	// No synchronization on session demanded at all...
	mav = invokeHandlerMethod(request, response, handlerMethod);
	//...
}
  1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		//...
		//参数解析器
		if (this.argumentResolvers != null) {
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		//返回值处理器
		if (this.returnValueHandlers != null) {
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}
		//...
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		//...
}

返回值处理器有这么15种,如下。
在这里插入图片描述

  1. org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
	//...
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	//...
	this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	//...
}
  1. org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	//选择合适的返回值处理器
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	//返回值处理器对返回值进行处理
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

selectHandler,遍历现有的返回值处理器,直到找到能够处理这种类型的返回值的处理器为止;
handleReturnValue,如果找到合适的返回值处理器,则处理器调用handleReturnValue方法处理返回值。

  1. org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}

进入HandlerMethodReturnValueHandler,该接口类有两个方法,如下。

public interface HandlerMethodReturnValueHandler {
	boolean supportsReturnType(MethodParameter returnType);
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

在这里插入图片描述
支持处理Person类型的返回值处理器是RequestResponseBodyMethodProcessor。

  1. org.springframework.web.servlet.mvc.method.annotation.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);
}
  1. org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
		ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
		throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		//...
		MediaType selectedMediaType = null;
		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) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
							//...
						}
					}
					else {
						//...
					}
					return;
				}
			}
		}

}

浏览器默认通过请求头(Accept)告诉服务器,自己能够接受什么样的内容类型。
在这里插入图片描述
服务端通过调用getAcceptableMediaTypes方法得知浏览器能够的内容类型,见acceptableTypes,有8种,如下。
在这里插入图片描述

服务器根据自身处理能力,决定自己能够生产什么样的内容类型。
服务器端通过调用getProducibleMediaTypes方法,确定自己能够生产的内容类型,见producibleTypes,有4种,如下。
在这里插入图片描述

现在,浏览器说,我能接受这8种内容类型;服务器说,我能生产这4种数据类型。二者协商下,嵌套for循环,得到mediaTypesToUse,如下。

以上4种类型,服务器能生产,浏览器也能接受。

接下来就是选择消息转换器,HttpMessageConverter。
SpringMVC默认支持10种消息转换器,如下。
在这里插入图片描述
进入HttpMessageConverter,其定义如下,包含5个方法。

public interface HttpMessageConverter<T> {
	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;

遍历当前系统现有的所有的消息转化器,找到能够将Person类转换为JSON的转换器:MappingJackson2HttpMessageConverter
在这里插入图片描述

  1. org.springframework.http.converter.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();
}
  1. org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
		throws IOException, HttpMessageNotWritableException {
	//...
	ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
	//...
	ObjectWriter objectWriter = (serializationView != null ?
					objectMapper.writerWithView(serializationView) : objectMapper.writer());
	//...
	objectWriter.writeValue(generator, value);
	writeSuffix(generator, object);
	generator.flush();
	//...
}

最后的最后,我们来了解下jackson jar、消息转换器、返回xml或json响应之间的关系。

先说结论:因为有jackson-databind、jackson-core jar包,所以消息转换器(能够将POJO转换为JSON或XML的消息转换器)才会被加入SpringMVC底层;SpringMVC底层有了这些消息转换器,才能将POJO转换为JSON或XML,并作为响应返回给浏览器。

现在来看源码,最好debug一下。
在这里插入图片描述

启动应用程序,自动配置类WebMvcAutoConfiguration会根据当前系统配置将现有的消息转换器加载到SpringMVC底层。其中,
只有当前系统包含com.fasterxml.jackson.databind.ObjectMappercom.fasterxml.jackson.core.JsonGenerator,变量jackson2Present才为truejackson2Presenttrue时,MappingJackson2HttpMessageConverter(能把POJO转换为JSON的消息转换器)才会加入到SpringMVC底层。
只有当前系统包含com.fasterxml.jackson.dataformat.xml.XmlMapper,变量jackson2XmlPresent才为truejackson2XmlPresenttrue时,MappingJackson2XmlHttpMessageConverter(能把POJO转换为XML的消息转换器)才会加入到SpringMVC底层。

  • WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#configureMessageConverters
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
	this.messageConvertersProvider
			.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
  • HttpMessageConverters#getConverters和HttpMessageConverters#getDefaultConverters
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
	List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
			addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
	//...
}

public List<HttpMessageConverter<?>> getConverters() {
	return this.converters;
}

private List<HttpMessageConverter<?>> getDefaultConverters() {
	List<HttpMessageConverter<?>> converters = new ArrayList<>();
	if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport",
			null)) {
		converters.addAll(new WebMvcConfigurationSupport() {

			public List<HttpMessageConverter<?>> defaultMessageConverters() {
				return super.getMessageConverters();
			}

		}.defaultMessageConverters());
	}
	//...
}

  • WebMvcConfigurationSupport#getMessageConverters
protected final List<HttpMessageConverter<?>> getMessageConverters() {
	if (this.messageConverters == null) {
		this.messageConverters = new ArrayList<>();
		configureMessageConverters(this.messageConverters);
		if (this.messageConverters.isEmpty()) {
			addDefaultHttpMessageConverters(this.messageConverters);
		}
		extendMessageConverters(this.messageConverters);
	}
	return this.messageConverters;
}
  • WebMvcConfigurationSupport#addDefaultHttpMessageConverters
//WebMvcConfigurationSupport
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
	private static final boolean jackson2Present;
	private static final boolean jackson2XmlPresent;
	static {
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
	}
	@Bean
	public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcValidator") Validator validator) {
		if (jackson2XmlPresent) {
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
		}
		if (jackson2Present) {
			Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();

			messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
		}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值