Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析
概述
在SpringMVC的使用时,往往会用到@RequestBody和@ResponseBody两个注解,尤其是处理ajax请求必然要使用@ResponseBody注解。这两个注解对应着Controller方法的参数解析和返回值处理,开始时都是只知其用,不知原理。我们来看个例子。
@RequestMapping("/requestBody")
public void requestBody(@RequestBody String body, Writer writer) throws IOException{
writer.write(body);
} @RequestMapping(value="/responseBody", produces="application/json")
@ResponseBody
public Map<String, Object> responseBody(){
Map<String, Object> retMap = new HashMap<>();
retMap.put("param1", "abc");
return retMap;
}
第一个requestBody请求,使用@RequestBody将HTTP请求体转换成String类型,第二个responseBody请求,将Map对象转换成json格式输出到HTTP响应中。这两个请求方法没有什么特殊,就是一个在参数前加了@RequestBody注解,一个在方法上加了@ResponseBody注解。而这两个注解是怎么完成HTTP报文信息同Controller方法中对象的转换的呢?
SpringMVC处理请求和响应时,支持多种类型的请求参数和返回类型,而此种功能的实现就需要对HTTP消息体和参数及返回值进行转换,为此SpringMVC提供了大量的转换类,所有转换类都实现了HttpMessageConverter接口。
public interface HttpMessageConverter<T> { // 当前转换器是否能将HTTP报文转换为对象类型
boolean canRead(Class<?> clazz, MediaType mediaType); // 当前转换器是否能将对象类型转换为HTTP报文
boolean canWrite(Class<?> clazz, MediaType mediaType); // 转换器能支持的HTTP媒体类型
List<MediaType> 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响应报文。
对应到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进行转换。流程如下:
SpringMVC为@RequestBody和@ResponseBody两个注解实现了统一处理类RequestResponseBodyMethodProcessor,实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler两个接口。
由上一篇文章我们可以知道,Controller方法被封装成ServletInvocableHandlerMethod类,并且由invokeAndHandle方法完成请求处理。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception { // 执行请求
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 返回值处理
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
} public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 参数解析
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
// invoke Controller方法
Object returnValue = doInvoke(args);
return returnValue;
}
在invoke Controller方法的前后分别执行了方法参数的解析和返回值的处理,我们分别来看。
参数解析
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length]; // 遍历所有参数,逐个解析
for (int i = ; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
} // 参数解析器解析HTTP报文到参数
if (this.argumentResolvers.supportsParameter(parameter)) {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
}
return args;
}
getMethodArgumentValues方法中的argumentResolvers就是多个HandlerMethodArgumentResolver的集合体,supportsParameter方法寻找参数合适的解析器,resolveArgument调用具体解析器的resolveArgument方法执行。
我们从RequestResponseBodyMethodProcessor看看@RequestBody的解析过程。RequestResponseBodyMethodProcessor的supportsParameter定义了它支持的参数类型,即必须有@RequestBody注解。
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
再来看resolveArgument方法
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional();
// 通过HttpMessageConverter读取HTTP报文
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);
}
具体实现由HttpMessageConverter来完成
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.canRead(targetClass, contentType)) {
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
// read方法执行HTTP报文到参数的转换
body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
}
break;
}
...
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
} .... return body;
}
代码部分省略了,关键部分即是遍历所有的HttpMessageConverter,通过canRead方法判断转换器是否支持对参数的转换,然后执行read方法完成转换。
返回值处理
完成Controller方法的调用后,在ServletInvocableHandlerMethod的invokeAndHandle中,使用返回值处理器对返回值进行转换。
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
这里的returnValueHandlers也是HandlerMethodReturnValueHandler的集合体HandlerMethodReturnValueHandlerComposite
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { // 选择合适的HandlerMethodReturnValueHandler,如果没有用@ResposeBody注解和用了注解其返回值处理器肯定不同
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,调用其supportsReturnType方法选择合适的HandlerMethodReturnValueHandler,然后调用其handleReturnValue方法完成处理。
这里还是以RequestResponseBodyMethodProcessor来分析下@ResponseBody的处理,它的具体实现在AbstractMessageConverterMethodProcessor抽象基类中。
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
RequestResponseBodyMethodProcessor要求方法上有@ResponseBody注解或者方法所在的Controller类上有@ResponseBody的注解。这就是常常用@RestController注解代替@Controller注解的原因,因为@RestController注解自带@ResponseBody。
handleReturnValue方法实际也是调用HttpMessageConverter来完成转换处理
public void handleReturnValue(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 <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { .... if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
// 判断是否支持返回值类型,返回值类型很有可能不同,如String,Map,List等
if (messageConverter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
// 执行返回值转换
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
...
}
return;
}
}
}
....
}
使用canWrite方法选择合适的HttpMessageConverter,然后调用write方法完成转换。
我们看看传入的参数 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
//获取HttpServletResponse
HttpServletResponse response = (HttpServletResponse)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; public ServletServerHttpResponse(HttpServletResponse servletResponse) {
Assert.notNull(servletResponse, "HttpServletResponse must not be null");
//将获取的HttpServletResponse作为ServletServerHttpResponse的属性值
this.servletResponse = servletResponse;
this.headers = new ServletServerHttpResponse.ServletResponseHttpHeaders();
}
} public interface ServletResponse {
String getCharacterEncoding(); String getContentType(); //ServletResponse有一个输出流对象,保存需要相应客户端的字节流
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();
}
我们具体看看 ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
protected void writeInternal(Object obj, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
//创建一个数组字节流缓冲对象
ByteArrayOutputStream outnew = new ByteArrayOutputStream();
//将obj对象转换成JSON并写入ByteArrayOutputStream中
int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), obj, this.fastJsonConfig.getSerializeConfig(), this.fastJsonConfig.getSerializeFilters(), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
headers.setContentLength((long)len);
//获取ServletResponse的输出流对象
OutputStream out = outputMessage.getBody();
//将转换后的outnew写入ServletResponse的输出流对象,这样就可以给客户端响应数据了
outnew.writeTo(out);
outnew.close();
} public OutputStream getBody() throws IOException {
this.bodyUsed = true;
this.writeHeaders();
//获取ServletResponse的输出流对象
//ServletOutputStream getOutputStream() throws IOException;
return this.servletResponse.getOutputStream();
}
最后我们看看JSON是怎么将obj对象转换成JSON对象的流
就是做一些循环拼接。
至此我们基本走完了一个HTTP请求报文经过处理后到HTTP响应报文的转换过程。现在你可能有个疑惑,SpringMVC我们都是开箱即用,这些参数解析器和返回值处理器在哪里定义的呢?在核心的HandlerAdapter实现类RequestMappingHandlerAdapter的初始化方法中定义的。
而在RequestMappingHandlerAdapter构造时,也同时初始化了众多的HttpMessageConverter,以支持多样的转换需求。
WebMvcConfigurationSupport.java protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false); messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
} if (jackson2XmlPresent) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
} if (jackson2Present) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
}
对于json或xml的转换方式,只要引入了jackson的依赖,即可自动发现,并注册相关的转换器。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.</version>
</dependency>
现在明白了SpringMVC做到了灵活又便捷的使用方式,其实在内部是做了大量的准备工作的。
Spring MVC源码(三) ----- @RequestBody和@ResponseBody原理解析的更多相关文章
- 精尽Spring MVC源码分析 - HandlerMapping 组件(三)之 AbstractHandlerMethodMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - 寻找遗失的 web.xml
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - 文章导读
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(一)之 AbstractHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(一)之 HandlerAdapter
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
随机推荐
- Delphi Android ActivityManager(提供了接口, 利用它可以方便的对Memory, Processes, Task, Service 等进行管)
ActivityManager: 对Activity交互提供了接口, 利用它可以方便的对Memory, Processes, Task, Service 等进行管理,. 这里对Delphi接口进行 ...
- vs2017 cordova调试android app
方案是:virtualbox + androidx86 7.0+_x64.iso虚拟机方式
- DIXML(包括所有的W3C XML标准)
Description:DIXml is an embedded XML, XSLT, and EXSLT processing library for Delphi (Embarcadero / C ...
- ML:吴恩达 机器学习 课程笔记(Week5~6)
Neural Networks: Learning Advice for Applying Machine Learning Machine Learning System Design
- Qt 使用 Google Breakpad 捕获程序崩溃报告(dump文件) good
http://blog.csdn.net/GoForwardToStep/article/details/56685810
- 【Qt】无边框窗体中带有ActiveX组件时的一个BUG
无意中发现的一个BUG,Qt5.1.1正式版首先创建一个GUI工程,拖入一个QAxWidget控件(为了使ActiveX生效,需要在.pro文件中加入CONFIG += qaxcontainer)接着 ...
- PHP开发框架 Laravel
Laravel 是一套简洁.优雅的PHP Web开发框架(PHP Web Framework).它可以让你从面条一样杂乱的代码中解脱出来:它可以帮你构建一个完美的网络APP,而且每行代码都可以简洁.富 ...
- DataVeryLite入门教程(一) 配置篇
DataVeryLite 是基于.net 4.0的数据库持久化ORM框架. 目前支持的数据库有Sqlserver,Mysql,Oracle,Db2,PostgreSql,Sqlite和Access. ...
- C语言实现常用排序算法——插入排序
插入排序是最基础的排序算法,原理: 首先1个元素肯定是有序的,所以插入排序从第二个元素开始遍历:内循环首先请求一个空间保存待插入元素,从当前元素向数组起始位置反向遍历:当发现有大于待插入元素的元素,则 ...
- You can't specify target table 'tbl_students' for update in FROM clause错误
此问题只出现在mysql中 oracle中无此问题 在同一语句中,当你在select某表的数据后,不能update这个表,如: DELETE FROM tbl_students WHERE id NO ...