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
类来具体执行解析过程,我们接着来看看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参数解析部分的代码。
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);
returnValueHandlers
为HandlerMethodReturnValueHandlerComposite
对象,该对象实现了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;}}
}
我们看到最终还是由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);}}@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).