消息转换器
前言
在使用SpringMVC的过程中,我们如果想要返回json格式的数据(Response的Content-Type: application/json;charset=UTF-8),通常使用@ResponseBody
注解,引入相应的json解析包就可以了,如果想要解析前端传来的json格式的数据(request header Content-Type为application/json),在Controller
的方法参数中加上@RequestBody
就可以了,非常的方便。那你有想过为什么加上这个注解就可以这么方便的开发了吗。
在上一篇文章介绍过HandlerMethodReturnValueHandler接口与HandlerMethodArgumentResolver接口后,我们可以来分析下为什么加上注解就可以完成json格式的数据和Java对象的转换了。
RequestResponseBodyMethodProcessor
当在Controller方法上使用了@RequestBody
注解时,处理方法参数的HandlerMethodArgumentResolver
实现类就是RequestResponseBodyMethodProcessor
,加上@ResponseBody
注解后,处理返回值的HandlerMethodReturnValueHandler
实现类也是RequestResponseBodyMethodProcessor
,该类既可以用来处理参数也可以用于处理返回值。
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return adaptArgumentIfNecessary(arg, parameter);
}
我们看到在处理参数的时候,有调用readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
,这是我们需要重点需要关注的,就是这个方法完成了json格式的数据到Java对象的转换。
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
//省略一坨代码
try {
inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
//如果是GenericHttpMessageConverter类型,且符合条件
if (genericConverter.canRead(targetType, contextClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
if (inputMessage.getBody() != null) {
//拿到匹配的RequestBodyAdvice,在读之前进行处理
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
//读
body = genericConverter.read(targetType, contextClass, inputMessage);
//读之后处理,类似AOP了,但是区别在于一个是在原对象上进行操作,一个是在代理对像上操作
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
}
break;
}
}
//...省略一坨代码
return body;
}
主要的流程就是先遍历HttpMessageConverter
实现类集合,找出第一个符合条件的HttpMessageConverter
实现类,由于是找出第一个符合条件的,所以,当有多个处理同一种类型信息的HttpMessageConverter
实现类存在的时候,我们是需要关心她的顺序的。当找到了符合条件的处理类后,还要在RequestResponseBodyAdviceChain
这个读写信息时候类似于切面的执行链中去寻找是否有处理当前类型信息的Advice,如果有的话,会在调用HttpMessageConverter#read()
方法的前后分别调用RequestBodyAdvice#beforeBodyRead()
和RequestBodyAdvice#afterBodyRead()
方法,这与Spring AOP的思想一致。最终将Http请求中的输入流转化为Java对象,依赖的就是HttpMessageConverter#read()
方法。
在本例中,由于request header Content-Type为application/json
,那么就会找到一个处理json格式数据的消息转换器来处理数据,将其转化为对应的Java对象。
消息转换器
在SpringMVC中,可以使用@RequestBody
和@ResponseBody
两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制。
我们知道,Http请求和响应报文本质上都是一串字符串,当请求报文来到Java世界,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。
我们从流中,只能读取到原始的字符串报文,同样,我们往输出流中,也只能写原始的字符。而在Java世界中,处理业务逻辑,都是以一个个有业务意义的对象为处理维度的,那么在报文到达SpringMVC和从SpringMVC出去,都存在一个字符串到Java对象的阻抗问题。这一过程,不可能由开发者手工转换。我们知道,在Struts2中,采用了OGNL来应对这个问题,而在SpringMVC中,它是HttpMessageConverter机制。
在SpringMVC执行流程中,大都是这样的设计,在每个执行点上,都会提供一个处理的集合,然后根据当前请求相应的信息来选择一个适合用来处理当前情况的处理类。所以SpringMVC中几个重要的处理组件接口,基本都会有一个方法,让实现类来限制他可以处理的当前请求相应的信息类型。HttpMessageConverter就是几个重要的处理组件接口中的一个。
HttpMessageConverter
public interface HttpMessageConverter<T> {
//指定可以处理的请求类型,通过请求头的Content-type和方法的参数类型来判断
boolean canRead(Class<?> clazz, MediaType mediaType);
//通过请求头的Accept和当前的方法返回类型来判断是否可以处理
boolean canWrite(Class<?> clazz, MediaType mediaType);
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
这个接口定义了四个方法,两个是实现类实现读写具体逻辑的方法,另外两个则是实现类定义该类可以处理的具体信息类型,还是SpringMVC固有的套路。
上一篇: 网站的记录密码功能时如何实现的?
下一篇: 开发webservice服务端的实例教程