SpringMVC @RequestBody和@ResponseBody原理解析

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).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 高性能缓存_高性能Java缓存----Caffeine

简单介绍Caffeine是新出现的一个高性能的Java缓存&#xff0c;有了它完全可以代替Guava Cache&#xff0c;来实现更加高效的缓存&#xff1b;Caffeine采用了W-TinyLFU回收策略&#xff0c;集合了LRU和LFU的优点&#xff0c;提供了一个最佳的命中率&#xff0c;在效率上可以秒杀…

@ResponseBody 转化成json后与实体类字段名不一致_SpringMVC字符串解析成json对象(@RequestBody注解和@ResponseBody注解)

ResponseBody 转化成json后与实体类字段名不一致 实体类A字段名由B改成C后&#xff0c;Controller 中返回的List中字段名仍然是C 经过ResponseBody返回到前台后又变成了B 后来发现公司项目采用的是阿里的fastjson&#xff0c; 是开源的Json格式化工具库 此工具库是根据实体类…

java togglebutton_ToggleButton和Switch使用大全

本文转载自&#xff1a;Android零基础入门第21节&#xff1a;ToggleButton和Switch使用大全http://www.apkbus.com/blog-205190-68463.html(出处: 安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户)&#xff0c;转载应备注出处&#xff0c;尊重原创上期学习了CheckB…

MyBatis之工作原理,简单实体的增加、修改、删除、查询_Mybatis-原理总结

一、MyBatis之工作原理 MyBatis是一个半自动映射框架。所谓半自动&#xff0c;是相对Hibernate全表映射而言的&#xff0c;MyBatis需要手动匹配提供POJO、SQL和映射关系。 我们知道&#xff0c;JDBC有四个核心对象&#xff1a; &#xff08;1&#xff09;DriverManager&#…

MySQL优化:如何避免回表查询?_什么是索引覆盖?

数据库表结构&#xff1a; create table user (id int primary key,name varchar(20),sex varchar(5),index(name) )engineinnodb;select id,name where nameshenjianselect id,name,sex where nameshenjian多查询了一个属性&#xff0c;为何检索过程完全不同&#xff1f; 什…

mysql提示Column count doesn‘t match value count at row 1错误

我们在对数据库进行添加信息时可能会遇到如下错误&#xff1a; Column count doesn’t match value count at row 1 该错误的意思是传入表的字段数和values值的个数不一样 我总结了一下&#xff0c;主要有3个易错点&#xff1a; 1.要传入表中的字段数和values后面的值的个数不…

java 阅发布模式_redis发布订阅模式

一 前言虽然有消息队列&#xff0c;我们还是要了解一下redis发布订阅模式哟&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;二发布订阅模式PUBLISH 命令向通道发送信息&#xff0c;此客户端称为publisher 发布者&#xff1b;SUBSCRIBE 向命令通道订阅信息&#…

把实体 转为json 数据格式---jackson 的详细用法_Jackson快速入门

首先介绍三个注解&#xff1a; JsonAutoDetect (method/field):作用于方法或字段&#xff0c;用来表明&#xff0c;当生成json的时候忽略有该annotation的方法或字段 JsonIgnore 过滤不需要转成json的属性 JsonIgnoreProperties 主要用于过滤掉一些不需要的属性 以上三个注…

java 类的加载顺序_Java 中类的加载顺序

这其实是去年校招时我遇到的一道阿里巴巴的笔试题(承认有点久远了-。-)&#xff0c;嗯&#xff0c;如果我没记错的话&#xff0c;当时是作为Java方向的一道选做大题。当然题意没有这么直白&#xff0c;题目只要求你写出程序运行后所有System.out.println的输出结果&#xff0c;…

Jackson转换json大写_关于jackson转化json的原理_jackson序列化和反序列化Json

背景 web工程中&#xff0c;数据交互是不可避免的&#xff0c;相比xml&#xff0c;json是现在流行的数据交互。 在调试接口中&#xff0c;发现返回字段的大小写不是我所期望的&#xff0c;原本应该返回的nNum字段变成了nnum&#xff0c;这样就导致和前端约定的有出入了。 ja…

svn利用TortoiseSVN忽略文件或文件夹(目录)

忽略已经版本控制的文件 如果你不小心添加了一些应该被忽略的文件&#xff0c;你如何将它们从版本控制中去除而不会丢失它们&#xff1f;或许你有自己的IDE配置文件&#xff0c;不是项目的一部分&#xff0c;但将会花费很多时间使之按照自己的方式工作。 如果你还没有提交&am…

SpringMVC 参数绑定详解

概述 记得之前跟前端同事联调接口的时候&#xff0c;后端SpringMVC需要接收数组类型的参数&#xff0c;然后跟前端说需要传数组类型过来。后来前端童鞋传了数组&#xff0c;但是后端接收不成功&#xff0c;联调失败。那时候由于时间关系没有仔细研究这块&#xff0c;当时想了个…

java 连接远程服务器_java实现连接远程服务器并执行命令的基本原理

一、所需jar包需要借助Ganymed SSH的jar包: ganymed-ssh2-build210.jar二、实现原理Ganymed SSH-2 java在整个访问过程中担当SSH的客户端&#xff0c;由于Linux系统自带SSH服务&#xff0c;所以可以直接访问Linux系统并执行相关命令&#xff0c;而 Windows系统则需要首先安装S…

SpringMVC接收基本类型和包装类型

先看一个示例&#xff1a; 注意这两个参数都没有加RequestParam注解。 测试&#xff1a; 直接报错。即没有加RequestParam注解&#xff0c;基本数据类型参数是必填的。 再测试&#xff1a; 直接返回null&#xff0c;也就是说包装类型参数&#xff0c;没有加RequestParam注解&…

SpringMVC 【参数绑定详讲、默认支持参数类型、自定义参数绑定、RequestParam 注解】

SpringMVC 第四篇【参数绑定详讲、默认支持参数类型、自定义参数绑定、RequestParam 注解】 参数绑定 我们在 Controller 使用方法参数接收值&#xff0c;就是把 web 端的值给接收到 Controller 中处理&#xff0c;这个过程就叫做参数绑定… 默认支持的参数类型 从上面的用…

MySQL中 IS NULL、IS NOT NULL、不等于, 能用上索引吗?

MySQL的WHERE子句中包含 IS NULL、IS NOT NULL、! 这些条件时便不能使用索引查询&#xff0c;只能使用全表扫描。 告诉大家结论&#xff1a; MySQL中决定使不使用某个索引执行查询的依据就是成本够不够小&#xff0c;如果null值很多&#xff0c;还是会用到索引的。 自己做了个…

Java EE 企业网站_基于jsp的企业网站系统-JavaEE实现企业网站系统 - java项目源码...

基于jspservletpojomysql实现一个javaee/javaweb的企业网站系统, 该项目可用各类java课程设计大作业中, 企业网站系统的系统架构分为前后台两部分, 最终实现在线上进行企业网站系统各项功能,实现了诸如用户管理, 登录注册, 权限管理等功能, 并实现对各类企业网站系统相关的实体…

MySQL创建联合索引,字段的先后顺序,对查询的影响分析

文章目录前言最左匹配原则为什么会有最左前缀呢&#xff1f;联合索引的存储结构联合索引字段的先后顺序b树可以存储的数据条数总结前言 ​ 对于联合索引我们知道&#xff0c;在使用的时候有一个最左前缀的原则&#xff0c;除了这些呢&#xff0c;比如字段放置的位置&#xff0…

php oracle 操作 sql语句中能不能添加数组_如何在PHP中使用Oracle数据库_php

在php3.0以上版本中&#xff0c;php内置了几乎目前所有的数据库处理函数&#xff0c;包括oracle;在本文中我们通过一个实例来介绍了如何使用这些函数来操作Oracle数据库。PHP提供了2大类API(应用程序接口)来操作Oracle数据库。一个是标准的Oracle处理函数(ORA) 另一个是Oracle …

mysql where过滤条件中and连接的两个条件的顺序不必和建立的联合索引的字段顺序一致_mysql and 顺序_mysql执行过程以及顺序

mysql中and的判断顺序 select * from a join b on 条件一 and 条件二条件一和二都是判断 id 字段&#xff0c; 条件一和条件二哪个先执行&#xff1f; 解析器会自动选择最优的流程执行的 这两个都是平级条件&#xff0c;理论上是没有先后顺序的&#xff01; 没有顺序&#xff…