SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口

一、概述

在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。HttpMessageConverter主要针对那些不会返回view视图的response。即方法含有@ResponseBody或者返回值为HttpEntity等类型的,它们都会用到HttpMessageConverter进行处理。

HttpMessageConverter

消息转换器最高层次的接口抽象,描述了一个消息转换器的一般特征,我们可以从这个接口中定义的方法,来领悟Spring3.x的设计者对这一机制的思考过程。

public interface HttpMessageConverter<T> {
        //指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为clazz类型的对象,
        //同时指定支持 MIME 类型(text/html,applaiction/json等)
	boolean canRead(Class<?> clazz, MediaType mediaType);

        //指定转换器是否可将clazz类型的对象写到响应流中,响应流支持的媒体类型在MediaType中定义
	boolean canWrite(Class<?> clazz, MediaType mediaType);

        //该转换器支持的媒体类型
	List<MediaType> getSupportedMediaTypes();

        //将请求信息流转换为T类型的对象
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

        //将T类型的对象写到响应流中,同时指定相应的媒体类型为contentType
	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;
}

HttpMessageConverter接口的定义出现了成对的canRead(),read()和canWrite(),write()方法,MediaType是对请求的Media Type属性的封装。举个例子,当我们声明了下面这个处理方法

@RequestMapping(value="/string", method=RequestMethod.POST)
public @ResponseBody String readString(@RequestBody String string) {
    return "Read string '" + string + "'";
}

在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。

当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。

我们可以用下面的图,简单描述一下这个过程。

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 1

RequestResponseBodyMethodProcessor

将上述过程集中描述的一个类是org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,这个类同时实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。前者是将请求报文绑定到处理方法形参的策略接口,后者则是对处理方法返回值进行处理的策略接口。两个接口的源码如下:

package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(MethodParameter parameter,
                           ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest,
                           WebDataBinderFactory binderFactory) throws Exception;

}
package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodReturnValueHandler {

    boolean supportsReturnType(MethodParameter returnType);

    void handleReturnValue(Object returnValue,
                           MethodParameter returnType,
                           ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest) throws Exception;

}

RequestResponseBodyMethodProcessor这个类,同时充当了方法参数解析和返回值处理两种角色。我们从它的源码中,可以找到上面两个接口的方法实现。

对HandlerMethodArgumentResolver接口的实现:

public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());

    String name = Conventions.getVariableNameForParameter(parameter);
    WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);

    if (argument != null) {
        validate(binder, parameter);
    }

    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

    return argument;
}

对HandlerMethodReturnValueHandler接口的实现:

public boolean supportsReturnType(MethodParameter returnType) {
    return returnType.getMethodAnnotation(ResponseBody.class) != null;
}

    public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException {

    mavContainer.setRequestHandled(true);
    if (returnValue != null) {
        writeWithMessageConverters(returnValue, returnType, webRequest);
    }
}

这两个接口的实现,分别是以是否有@RequestBody和@ResponseBody为条件,然后分别调用HttpMessageConverter来进行消息的读写。

SpringMVC默认搭载了HttpMessageConverter的实现类有6个,查看DispatchServlet源码

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 3

我们发现这里有7个HttpMessageConverter的实现类,最后一个是因为添加了jackson的jar包后出现的。不信,你可以试着把jar包都去掉,默认的SpringMVC装载的就只有6个实现类。

使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:

  • 使用 @RequestBody / @ResponseBody  对处理方法进行标注
  • 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值

当控制器处理方法使用到 @RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T> 时, Spring 首先根据请求头或响应头的Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的HttpMessageConverter 将报错

@RequestBody 和 @ResponseBody 不需要成对出现。

下面我们针对@RequestBody 和 @ResponseBody 这两个注解进行相关案例的使用和源码解读。

二、案例

♦@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。

通过@requestBody可以将请求体中的JSON字符串绑定到相应的bean上,当然,也可以将其分别绑定到对应的字符串上。这种情况是将JSON字符串中的两个变量的值分别赋予了两个字符串,但是假如我有一个User类,拥有如下字段:String userName;    String pwd;
那么上述参数可以改为以下形式:@requestBody User user 这种形式会将JSON字符串中的值赋予user中对应的属性上。需要注意的是:JSON字符串中的key必须对应user中的属性名,否则是请求不过去的。

♦Product实体类

public class Product {
	// 编码
	private String number;
	// 库存
	private Integer qty;
	//上架时间
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date ontime;
	
	@NumberFormat(pattern="#,###,###.#")
	private Float price;
	
	public Product() {
		
	}

	public Product(String number, Integer qty, Date ontime, Float price) {
		this.number = number;
		this.qty = qty;
		this.ontime = ontime;
		this.price = price;
	}

	public Float getPrice() {
		return price;
	}

	public void setPrice(Float price) {
		this.price = price;
	}

	public Date getOntime() {
		return ontime;
	}

	public void setOntime(Date ontime) {
		this.ontime = ontime;
	}

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	public Integer getQty() {
		return qty;
	}

	public void setQty(Integer qty) {
		this.qty = qty;
	}

	@Override
	public String toString() {
		return "Product [number=" + number + ", qty=" + qty + ", ontime="
				+ ontime + ", price=" + price + "]";
	}

}

♦处理器ProductHandler

@RequestMapping("/product")
@Controller
public class ProductHandler {
	@ResponseBody
	@RequestMapping(value = "/saveProduct", method = RequestMethod.POST)
	public ReturnMessage saveProduct(@RequestBody Product product) {
		//经过转换之后的产品实体类
		System.out.println("保存产品成功:" + product);
		return new ReturnMessage(true, "保存成功!");
	}
}

♦返回处理类ReturnMessage

public class ReturnMessage {
	private boolean flag;
	private String outMes;

	public ReturnMessage() {

	}

	public ReturnMessage(boolean flag, String outMes) {
		super();
		this.flag = flag;
		this.outMes = outMes;
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

	public String getOutMes() {
		return outMes;
	}

	public void setOutMes(String outMes) {
		this.outMes = outMes;
	}

	@Override
	public String toString() {
		return "ReturnMessage [flag=" + flag + ", outMes=" + outMes + "]";
	}

}

♦JSP页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>测试页面</title>
<script type="text/javascript" src="/js/jquery-easyui-1.4.1/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/js/jquery-easyui-1.4.1/jquery.serializejson.js"></script>
<script type="text/javascript">
	function doSubmit() {
		$.ajax({
			type : "POST",
			dataType : "json",
			url : "/product/saveProduct",
			contentType : "application/json;charset=utf-8",
			data : JSON.stringify($('#form1').serializeJSON()),
			success : function(result) {
				console.info(result);
				if (result.flag == true) {
					alert(result.outMes);
				}
			},
			error : function() {
				alert("异常!");
			}
		});
	}
</script>
</head>
<body>
	<form id="form1" onsubmit="return false" action="##" method="post">
		编码: <input type="text" name="number" /> <br> 
		库存: <input type="text" name="qty" /> <br> 
		上架时间: <input type="text" name="ontime" /> <br> 
		价格: <input type="text" name="price" /> <br>
		<input type="button" value="提交" onclick="doSubmit();" />
	</form>
</body>
</html>

♦启动运行结果如下:

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 5

运行成功。

♦下面我们从源码角度,查看一下整个数据处理流程,JSON数据是如何通过HttpMessageConverter转换成JavaBean类,以及输出时JavaBean类是如何通过HttpMessageConverter转换成 JSON数据

如图,在20行处打上断点

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 7

前台点击提交按钮

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 9

运行完代码。

我们再来Debug调试一下代码,点击提交按钮

777行处打上断点

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 11

F5进入该方法,进入ServletInvocableHandlerMethod类的invokeAndHandle方法

该方法内部,有两块值得注意的地方,如下图框框:

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 13

F5进入invokeForRequest方法

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 15

F5进入getMethodArgumentValues方法内部

这段方法对获取到的方法参数进行遍历处理,大家要关注的是args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);这段方法

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 17

在上述红框框代码处,打上断点

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 19

F5进入该方法,在77行出断点,F8断点到此处

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 21

然后F5继续Debug

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 23

上图红框框处代码:是我们这节要说的核心代码位置,即读取信息通过MessageConverter

实际上最后调用的是AbstractMessageConverterMethodArgumentResolver类的readWithMessageConverters方法

在RequestResponseBodyMethodProcessor类的184行处打上断点,F8进入该方法

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 25

然后F5进入AbstractMessageConverterMethodArgumentResolver类的readWithMessageConverters方法

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 27

上图,最核心的代码就是红框框里面的这部分代码,下面将抽取这部分代码做如下解释

//this.messageConverters指的是SpringMVC装载的messageConverters,总共有7个,对这7个Converters进行遍历
for (HttpMessageConverter<?> converter : this.messageConverters) {
	//如果Converters是GenericHttpMessageConverter的实例就往下面走,GenericHttpMessageConverter有三个实现类,
	//分别是AbstractJackson2HttpMessageConverter,Jaxb2CollectionHttpMessageConverter,GsonHttpMessageConverter,
	//其中AbstractJackson2HttpMessageConverter有两个实现类MappingJackson2HttpMessageConverter,MappingJackson2XmlHttpMessageConverter
	//现在系统SpringMVC装载的messageConverters就含有MappingJackson2HttpMessageConverter
	if (converter instanceof GenericHttpMessageConverter) {
		GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
		if (genericConverter.canRead(targetType, contextClass, contentType)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Reading [" + targetType + "] as \"" +
						contentType + "\" using [" + converter + "]");
			}
			//将JSON数据处理成对应的JavaBean
			return genericConverter.read(targetType, contextClass, inputMessage);
		}
	}
	//获取方法参数的类型
	Class<T> targetClass = (Class<T>)
			ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
	//判断是否可读,这里面会拿到targetClass跟converter,需要跟入源码对每一个Converter的实现方法
        //进行查看
	if (converter.canRead(targetClass, contentType)) {
		if (logger.isDebugEnabled()) {
			logger.debug("Reading [" + targetClass.getName() + "] as \"" +
					contentType + "\" using [" + converter + "]");
		}
		return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
	}
}

如上代码走完之后,会将JSON数据处理成对应的JavaBean对象

同样的,输出响应时,也会有一个类似上面的方法来处理返回的结果,这段方法在AbstractMessageConverterMethodProcessor类的writeWithMessageConverters方法里面,只要是方法被标记了@ResponseBody,最后结果处理都会调用这段方法,用以判断使用哪种HttpMessageConverter来处理要返回何种数据。

SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口的照片 - 29

后面半段,如何处理返回的结果,大家可以按照上面的流程进行源码Debug,这里就不再赘述了。

至此,我们关于SpringMVC教程之处理JSON数据原理-HttpMessageConverter接口介绍完毕。想要了解更多关于SpringMVC开发的教程,请参考http://www.marsitman.com/springmvc
博客地址:http://www.marsitman.com/springmvc/springmvc-converter.html
版权声明:本文为博主原创文章,允许转载,但转载必须标明出处。

 

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!



点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注