SpringMVC @RequestBody和@ResponseBody原理解析

前言

@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<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响应报文。

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进行转换。流程如下:

@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;
}

其中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类来具体执行解析过程,我们接着来看看RequestResponseBodyMethodProcessorresolveArgument方法又是怎样的一个处理过程。

不同的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参数解析部分的代码。

protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);for (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) 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<T>) 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);
returnValueHandlersHandlerMethodReturnValueHandlerComposite对象,该对象实现了HandlerMethodReturnValueHandler接口,我们接着来看看handleReturnValue方法的具体实现。

 @Overridepublic 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,用来处理返回值。

我们看到的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 <T> 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;}}
}

我们看到最终还是由HttpMessageConverterAbstractGenericHttpMessageConverter实现类)的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);}}@Overridepublic OutputStream getBody() throws IOException {this.bodyUsed = true;writeHeaders();return this.servletResponse.getOutputStream();}

objectWriter.writeValue(generator, value)方法中将value对象通过serialize序列化方法,将对象转为json字符串,然后设置到io流中。我们最后看看Jackson最终的序列化是怎么样的?

@Overridepublic 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<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());}
}

五、相关依赖
大家可能会发现springboot项目都没有jackson相关的依赖,那为什么可以进行jackson的序列化呢,那是因为在spring-boot-starter-web依赖中其实已经包含了jackson相关的依赖。

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.8</version><scope>compile</scope><optional>true</optional></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-cbor</artifactId><version>2.9.8</version><scope>compile</scope><optional>true</optional></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-smile</artifactId><version>2.9.8</version><scope>compile</scope><optional>true</optional></dependency><dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.9.8</version><scope>compile</scope><optional>true</optional></dependency>

总结

看似简简单单的@RequestBody@ResponseBody两个注解,其实内部做了大量的准备工作。现在童鞋们明白这整个过程的实现原理吧。



举例使用

@RequestBody是把整个HttpServletRequest的输入(request.getInputStream()),转换成一个对象,常用的转换是采用json方式,在spring中是RequestResponseBodyMethodProcessor利用HttpMessageConventer做的。

前端代码必须采用POST请求(GET请求的输入是空):

<!DOCTYPE html>
<html>
<head><title></title><script type="text/javascript" src="http://www.w3school.com.cn/jquery/jquery-1.11.1.min.js"></script><script type="text/javascript">$(document).ready(function(){  var saveDataAry=[];  var data1={"userName":"test","address":"gz"};  var data2={"userName":"ququ","address":"gr"};  saveDataAry.push(data1);  saveDataAry.push(data2);         $.ajax({ type:"POST",url:"http://localhost:8080/test/mvc/saveUser", dataType:"json",      contentType:"application/json",               data:JSON.stringify(saveDataAry), success:function(data){ alert("succes" +data);                   },error:function(argument) {alert("error" + argument);}}); });  </script>
</head>
<body>test
</body>
</html>

后端代码

@Controller
public class UserController {@RequestMapping(value = "saveUser")@ResponseBodypublic String saveUser(@RequestBody List<User> users) {System.out.println("UserController.saveUser users size:"+users.size());return "ok";}
}

整体springmvc 调用链路

@RequestBody

在处理参数时候各种HandlerMethodArgumentResolver来解决参数实际需要传递参数,其中默认的有24中;而处理@RequestBody的是RequestResponseBodyMethodProcessor。相对的,处理@CookieValue注释的则是ServletCookieValueMethodArgumentResolver。

@ResponseBody

而responseBody同样是RequestResponseBodyMethodProcessor作为HandlerMethodReturnValueHandler,处理实际执行的controller HandlerMethod函数的return value.

public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException {mavContainer.setRequestHandled(true);  //最重要一步,设置请求已经处理过if (returnValue != null) {writeWithMessageConverters(returnValue, returnType, webRequest);}}

RequestResponseBodyMethodProcessor 的handleReturnValue()函数处理时,首先ModelAndViewContainer.requestHandled设置成功,表明整个http请求已经处理,后续就不会执行veiw渲染了(跳过render view).

SpringMVC @RequestBody和@ResponseBody原理解析相关推荐

  1. Spring MVC源码 ----- @RequestBody和@ResponseBody原理解析

    来源:https://www.cnblogs.com/java-chen-hao/p/11187914.html 1. 概述 在SpringMVC的使用时,往往会用到@RequestBody和@Res ...

  2. SpringMVC @RequestBody和@ResponseBody注解 以及 Ajax异步请求

    实例讲解: 1.先创建一个实体类Student: public class Student {private String sname;private String password;private ...

  3. 【SpringMVC】SpringMVC: @RequestBody 和@ResponseBody 注解详解 NoHandlerFoundException

    文章目录 1.美图 2.概述 3.@RequestBody 3.1 使用时机 4.@ResponseBody 4.1 错误案例 4.2 苦苦寻找 4.3 思考升华 5.HttpMessageConve ...

  4. java学习笔记——springmvc 之 数据自定义转换器 数据格式化 JSR303数据校验返回与接收JSON(@RequestBody 和 @ResponseBody)

    九.数据绑定流程分析 1.提出问题 日期字符串格式的表单参数,提交后转换为Date类型 <!--    解决问题: 1.数据类型转换 2.数据格式 3.数据校验 --> BirthDay ...

  5. SpringMVC源码之参数解析绑定原理

    摘要 本文从源码层面简单讲解SpringMVC的参数绑定原理 SpringMVC参数绑定相关组件的初始化过程 在理解初始化之前,先来认识一个接口 HandlerMethodArgumentResolv ...

  6. 在SpringMVC中使用@RequestBody和@ResponseBody注解处理json时,报出HTTP Status 415的解决方案

    在SpringMVC中使用@RequestBody和@ResponseBody注解处理json时,报出HTTP Status 415的解决方案 参考文章: (1)在SpringMVC中使用@Reque ...

  7. 【2021软件创新实验室暑假集训】SpringMVC框架(设计原理、简单使用、源码探究)

    系列文章目录 20级 Java篇 [2021软件创新实验室暑假集训]计算机的起源与大致原理 [2021软件创新实验室暑假集训]Java基础(一) [2021软件创新实验室暑假集训]Java基础(二) ...

  8. 002 第一季SpringBoot2核心技术-核心功能:配置文件、Web开发(原生组件)、数据访问、单元测试、指标监控、原理解析:@Value、命令行参数、手动获取bean、自定义starter

    三.核心技术之- ->核心功能 1. 配置文件 1.1 文件类型 1.1.1 properties 同以前的properties用法 优先级高于yml的方式. 1.1.2 yaml 1) 简介 ...

  9. SpringBoot @Validated原理解析

    文章目录 一.开发使用`@Validated` 出现问题 1.1 代码 1.2 请求 1.3 响应 二.源码: RequestResponseBodyMethodProcessor.resolveAr ...

最新文章

  1. php 文件限速下载代码
  2. C#进阶系列——WebApi 接口参数不再困惑:传参详解
  3. java怎么缓存行填充_为什么java的Exchanger.Slot缓存行填充像这样?
  4. eclipse 界面设置与字体更改
  5. 重写、重构、重载区别
  6. 3-3:常见任务和主要工具之网络
  7. java 注解报错_eclipse编译项目:Java @Override 注解报错的解决方法
  8. (转)TweenMax动画库学习(四)
  9. java匿名对象_面向对象
  10. 加密保护软件 WinLicense常见问题整理大全(四)
  11. 【数据结构(C语言)】数据结构-图
  12. cpp的vector初始化方法
  13. 职业生涯规划书-前端
  14. python编程好学吗-python语言好学吗
  15. 初中计算机表格处理教案,中学信息技术教案《制作excel表格》
  16. 哪些东西买了之后,会让人因生活质量和幸福感提升而感觉相见恨晚?
  17. 真香,华为主动离职也给 N+1
  18. HDU 2188 经典巴什博弈
  19. ffmpeg开发 av_init_packet()和av_new_packet()
  20. 想做刷爆TikTok的短视频?这8种特效一定要用到

热门文章

  1. 如何让百度搜索收录自己的Hexo博客文章
  2. 5000词学英语——DAY8
  3. 终极三国 片头曲-对手歌词 片尾曲-够爱歌词
  4. leetcode 974. Subarray Sums Divisible by K
  5. 从头开始学习React
  6. Git 简单使用教程
  7. python 隐藏进程_运行进程隐藏的Python
  8. 转载:那些年他(她)们做过的“蠢事”
  9. EPROM工作原理2
  10. php v11跟v14区别,独家揭秘真相戴森v11absolute和fluffy哪个好?有啥区别?老司机指教诉说...