@RequestBody作用是将http请求解析为对应的对象。例如:
http请求的参数(application/json格式):
{
"accountId": 10,
"adGroupId": "12345678",
"campaignId": "12345678",
"dataType": 0,
"sign": "abcdefg",
"site": "us",
"timeStamp": 1453250,
"userId": 10
}
通过@RequestBody可以解析为ProductSyncNegativeDto对象(如下代码所示)
public CustResponse syncNegative(@RequestBody ProductSyncNegativeDto productSyncNegativeDto)
那@RequestBody注解是如何实现http请求报文转对象的呢?
@ResponseBody作用是将返回的对象转为json字符串,例如我们返回一个CustResponse对象,那postman中的结果会是啥?
{
"code": 100,
"msg": "",
"details": [
10,
10,
"us",
"12345678",
"12345678",
0
]
}
我们可以发现,结果是一个json字符串,那@ResponseBody注解到底是如何将对象转为json字符串返回的呢?
接下来老师会带童鞋们一些来揭秘,@RequestBody、@ResponseBody的底层实现原理。
一、概述
@Controller注解
在开始之前,我们先来介绍一下@Controller,做过ssm/ssh项目的同学肯定都接触过springMVC,那必然会用到@Controller注解。Controller方法被封装成ServletInvocableHandlerMethod类,并且由invokeAndHandle方法完成请求处理。
HttpMessageConverter
SpringMVC处理请求和响应时,支持多种类型的请求参数和返回类型,而此种功能的实现就需要对HTTP消息体和参数及返回值进行转换,为此SpringMVC提供了大量的转换类,所有转换类都实现了HttpMessageConverter接口。
public interface HttpMessageConverter {
// 当前转换器是否能将HTTP报文转换为对象类型
boolean canRead(Class> clazz, MediaType mediaType);
// 当前转换器是否能将对象类型转换为HTTP报文
boolean canWrite(Class> clazz, MediaType mediaType);
// 转换器能支持的HTTP媒体类型
List getSupportedMediaTypes();
// 转换HTTP报文为特定类型
T read(Class extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 将特定类型对象转换为HTTP报文
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
HttpMessageConverter接口定义了5个方法,用于将HTTP请求报文转换为java对象,以及将java对象转换为HTTP响应报文。
HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler
对应到SpringMVC的Controller方法,read方法即是读取HTTP请求转换为参数对象,write方法即是将返回值对象转换为HTTP响应报文。SpringMVC定义了两个接口来操作这两个过程:参数解析器HandlerMethodArgumentResolver和返回值处理器HandlerMethodReturnValueHandler。
// 参数解析器接口
public interface HandlerMethodArgumentResolver {
// 解析器是否支持方法参数
boolean supportsParameter(MethodParameter parameter);
// 解析HTTP报文中对应的方法参数
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
// 返回值处理器接口
public interface HandlerMethodReturnValueHandler {
// 处理器是否支持返回值类型
boolean supportsReturnType(MethodParameter returnType);
// 将返回值解析为HTTP响应报文
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
参数解析器和返回值处理器在底层处理时,都是通过HttpMessageConverter进行转换。流程如下:
[图片上传失败...(image-10814b-1608287465691)]
二、@RequestBody解析过程
所有的http请求都会进入ServletInvocableHandlerMethod类(继承InvocableHandlerMethod,所有的参数解析器都会在在这里面进行初始化)的invokeAndHandle方法中,我们来具体看看invokeAndHandle方法是干什么的。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 执行http请求
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
// 返回值处理
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}catch (Exception ex) {
if (logger.isTraceEnabled()){
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
我们可以看到invokeAndHandle方法都会进入invokeForRequest方法中,invokeForRequest方法就是实现@RequestBody注解的功能,将http请求报文解析为我们设置的对象。我们进入该方法看看,里面具体做了哪些事情。
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// http报文解析为对象数组
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//执行@PostMapping、@GetMapping等接口
return doInvoke(args);
}
我们可以看到invokeForRequest中主要做了两件事情,一个是通过getMethodArgumentValues方法返回http解析后的对象数组,然后通过doInvoke方法执行接口的具体业务逻辑代码。
我们接着进入getMethodArgumentValues方法,细看一下@RequestBody的具体解析过程。
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取http请求参数
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
// 遍历所有参数,挨个解析
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
// 参数解析器解析HTTP报文
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
[图片上传失败...(image-5b2c58-1608287465691)]
其中this.resolvers.supportsParameter(parameter)用来判断请求参数是否合法,this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)方法最终实现@RequestBody解析操作。我们来看看this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory)中做了什么。
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
// 获取对应的解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException(
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
" supportsParameter should be called first.");
}
// 通过HandlerMethodArgumentResolver 解析器解析http报文
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
getArgumentResolver方法来获取对应的HandlerMethodArgumentResolver参数解析器,参数解析器最终通过RequestResponseBodyMethodProcessor类来具体执行解析过程,我们接着来看看RequestResponseBodyMethodProcessor中resolveArgument方法又是怎样的一个处理过程。
不同的resolvers(HandlerMethodArgumentResolver接口)会对应不同的参数解析器,例如public String testDemo(String name),解析器就会变成ServletRequestMethodArgumentResolver,如果是@RequestBody,参数解析器就是RequestResponseBodyMethodProcessor
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional(); // 通过HttpMessageConverter来解析http报文为Object对象 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null) { 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()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return adaptArgumentIfNecessary(arg, parameter);
}
readWithMessageConverters方法中,HttpMessageConverter(接口对应实现类)的read方法实现了http报文解析,我们来看看最终http参数解析部分的代码。
protectedObject readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {.... try { message = new EmptyBodyCheckingHttpInputMessage(inputMessage); for (HttpMessageConverter> converter : this.messageConverters) { Class
> converterType = (Class >) converter.getClass(); GenericHttpMessageConverter> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter>) converter : null); // 判断转换器是否支持参数类型 if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); // read方法执行HTTP报文到参数的转换 body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter ) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); }else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } } }catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage); } .... }
代码部分省略了,关键部分即是遍历所有的HttpMessageConverter,然后通过canRead方法判断解析器是否支持,最后执行AbstractJackson2HttpMessageConverter对象(HttpMessageConverter实现类)的read方法完成最后的参数解析。
AbstractJackson2HttpMessageConverter对象的read方法,核心是利用了jackson工具,将http报文的json字符串转换为object对象并返回。
三、@ResponseBody返回值序列化过程
执行完doInvoke逻辑代码之后,通过ServletInvocableHandlerMethod对象的invokeAndHandle方法,利用返回值处理器对返回值进行序列化输出。
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
returnValueHandlers为HandlerMethodReturnValueHandlerComposite对象,该对象实现了HandlerMethodReturnValueHandler接口,我们接着来看看handleReturnValue方法的具体实现。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 选择合适的HandlerMethodReturnValueHandler返回值处理器
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方法会提供合适的HandlerMethodReturnValueHandler,用来处理返回值。
[图片上传失败...(image-84a908-1608287465691)]
[图片上传失败...(image-f81acf-1608287465691)]
正在上传…重新上传取消
[图片上传失败...(image-46e13d-1608287465691)]
我们看到的HandlerMethodReturnValueHandler处理器最终也是由RequestResponseBodyMethodProcessor实现的,我们具体来看看handleReturnValue方法。
handler(HandlerMethodReturnValueHandler)接口会根据不同类型选择不同的返回值处理器,例如页面跳转类型的处理器就是ViewNameMethodReturnValueHandler。
@Override
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); // 调用HttpMessageConverter执行 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
protected
void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
....
for (HttpMessageConverter> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter>) converter : null);
// 判断是否支持返回值类型,返回值类型很有可能不同,如String,Map,List等
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 {
// 执行返回值转换
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}....
}
我们看到最终还是由HttpMessageConverter(AbstractGenericHttpMessageConverter实现类)的write方法来进行对象的序列化输出。
大家都知道@ResponseBody需要通过io流来读取,也就HttpMessageConverter最终的write会写入到io输出流中,上面的createOutputMessage(webRequest)方法就是创建一个输出流,我们来具体看看它的实现。
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
}public class ServletServerHttpResponse implements ServerHttpResponse {
private final HttpServletResponse servletResponse; private final HttpHeaders headers; private boolean headersWritten = false; private boolean bodyUsed = false; /** * Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}. * @param servletResponse the servlet response */ public ServletServerHttpResponse(HttpServletResponse servletResponse) { Assert.notNull(servletResponse, "HttpServletResponse must not be null"); this.servletResponse = servletResponse; this.headers = new ServletResponseHttpHeaders(); }
}
public interface ServletResponse {
String getCharacterEncoding();String getContentType(); ServletOutputStream getOutputStream() throws IOException; PrintWriter getWriter() throws IOException; void setCharacterEncoding(String var1); void setContentLength(int var1); void setContentLengthLong(long var1); void setContentType(String var1); void setBufferSize(int var1); int getBufferSize(); void flushBuffer() throws IOException; void resetBuffer(); boolean isCommitted(); void reset(); void setLocale(Locale var1); Locale getLocale();
}
createOutputMessage方法中创建了ServletServerHttpResponse ,然后通过 ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage)方法写入到输出流中。write方法的核心也是通过Jackson工具将对象解析为json字符串。我们最后来看看write的核心处理方法writeInternal。
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType(); JsonEncoding encoding = getJsonEncoding(contentType); JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding); try { writePrefix(generator, object); Object value = object; Class> serializationView = null; FilterProvider filters = null; JavaType javaType = null; if (object instanceof MappingJacksonValue) { MappingJacksonValue container = (MappingJacksonValue) object; value = container.getValue(); serializationView = container.getSerializationView(); filters = container.getFilters(); } if (type != null && TypeUtils.isAssignable(type, value.getClass())) { javaType = getJavaType(type, null); } ObjectWriter objectWriter = (serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer()); if (filters != null) { objectWriter = objectWriter.with(filters); } if (javaType != null && javaType.isContainerType()) { objectWriter = objectWriter.forType(javaType); } SerializationConfig config = objectWriter.getConfig(); if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) { objectWriter = objectWriter.with(this.ssePrettyPrinter); } objectWriter.writeValue(generator, value); writeSuffix(generator, object); generator.flush(); } catch (InvalidDefinitionException ex) { throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex); } } @Override public OutputStream getBody() throws IOException { this.bodyUsed = true; writeHeaders(); return this.servletResponse.getOutputStream(); }
[图片上传失败...(image-64b9a8-1608287465691)]
objectWriter.writeValue(generator, value)方法中将value对象通过serialize序列化方法,将对象转为json字符串,然后设置到io流中。我们最后看看Jackson最终的序列化是怎么样的?
@Override
public final void serialize(Object bean, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
if (_objectIdWriter != null) {
gen.setCurrentValue(bean); // [databind#631]
_serializeWithObjectId(bean, gen, provider, true);
return;
}
// 设置json的开始符号("{")
gen.writeStartObject(bean);
if (_propertyFilterId != null) {
// 循环将对象设置为json字符串 serializeFieldsFiltered(bean, gen, provider);
} else {
serializeFields(bean, gen, provider);
}
// 设置json的结束符号("}")
gen.writeEndObject();
}
在serialize方法中通过JsonGenerator将要返回的对象转为json格式的字符串。
四、springMVC初始化
至此我们就基本走完了一个HTTP请求和响应的过程。现在你可能有个疑惑,SpringMVC我们都是开箱即用,这些参数解析器和返回值处理器在哪里定义的呢?在核心的HandlerAdapter实现类RequestMappingHandlerAdapter的初始化方法中定义的。
而在RequestMappingHandlerAdapter构造时,也同时初始化了众多的HttpMessageConverter,以支持多样的转换需求。
WebMvcConfigurationSupport.javaprotected final void addDefaultHttpMessageConverters(List
> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter
}
五、相关依赖
大家可能会发现springboot项目都没有jackson相关的依赖,那为什么可以进行jackson的序列化呢,那是因为在spring-boot-starter-web依赖中其实已经包含了jackson相关的依赖。
com.fasterxml.jackson.core
jackson-databind
2.9.8
compile
true
com.fasterxml.jackson.dataformat
jackson-dataformat-cbor
2.9.8
compile
true
com.fasterxml.jackson.dataformat
jackson-dataformat-smile
2.9.8
compile
true
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
2.9.8
compile
true
六、总结
看似简简单单的@RequestBody和@ResponseBody两个注解,其实内部做了大量的准备工作。现在童鞋们明白这整个过程的实现原理吧。
参考:
https://cloud.tencent.com/developer/article/1611093