使用

ResponseEntity可以作为controller的返回值,比如对于一个处理下载二进制文件的接口,可以这么定义:

    @RequestMapping("/download")public ResponseEntity<byte[]> download(@RequestParam String fileName) throws IOException {byte[] bytes = xxx;return new ResponseEntity<>(bytes, headers, HttpStatus.OK);}

也可以使用流式风格的构造器:

    @RequestMapping("/download")public ResponseEntity<byte[]> download(@RequestParam String fileName) throws IOException {byte[] bytes = xxx;return ResponseEntity.ok().headers(headers).body(bytes);}        

当然,ResponseEntity不仅仅可以用于处理下载,非ModelAndView的其他场景均可以使用。在WEB API项目中,如果没有特定的Java Bean封装的返回类型,可以使用该类型。

原理

那么这个ResponseEntity到底是啥?SpringMVC框架又是如何处理的?

public class HttpEntity<T> {private final HttpHeaders headers;@Nullableprivate final T body;// 省略其他代码}

ResponseEntity继承了HttpEntity类,HttpEntity代表一个http请求或者响应实体,其内部有两个成员变量:header及body,代表http请求或响应的header及body,其中的body是泛化的。

public class ResponseEntity<T> extends HttpEntity<T> {private final Object status;public ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status) {super(body, headers);Assert.notNull(status, "HttpStatus must not be null");this.status = status;}// 省略其他代码
}

下面看下ResponseEntity类,扩展了HttpEntity类,新增了status成员变量,这样,一个ResponseEntity基本可以代表完整的http的请求或响应了。这其实就是ResponseEntity类的作用。

使用ResponseEntity作为controller的返回值,我们可以方便地处理响应的header,状态码以及body。而通常使用的@ResponseBody注解,只能处理body部分。这也是为什么通常在下载场景中会使用ResponseEntity,因为下载需要设置header里的content-type以及特殊的status(比如206)。

HttpEntityMethodProcessor

该类继承自AbstractMessageConverterMethodProcessor类,AbstractMessageConverterMethodProcessor类我们之前在介绍@Responsebody注解原理时提到过,其中RequestResponseBodyMethodProcessor是用来处理@Responsebody注解的。这里的HttpEntityMethodProcessor是AbstractMessageConverterMethodProcessor的另一个子类。该抽象类常见的子类其实就这俩了。在HttpEntityMethodProcessor中:

 @Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));}

可以看到,该processor专门处理返回值类型是ResponseEntity类型的controller返回值。处理过程如下:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {mavContainer.setRequestHandled(true);if (returnValue == null) {return;}ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);Assert.isInstanceOf(HttpEntity.class, returnValue);HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;HttpHeaders outputHeaders = outputMessage.getHeaders();HttpHeaders entityHeaders = responseEntity.getHeaders();if (!entityHeaders.isEmpty()) {entityHeaders.forEach((key, value) -> {if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);if (!values.isEmpty()) {outputHeaders.setVary(values);}}else {outputHeaders.put(key, value);}});}if (responseEntity instanceof ResponseEntity) {int returnStatus = ((ResponseEntity<?>) responseEntity).getStatusCodeValue();outputMessage.getServletResponse().setStatus(returnStatus);if (returnStatus == 200) {if (SAFE_METHODS.contains(inputMessage.getMethod())&& isResourceNotModified(inputMessage, outputMessage)) {// Ensure headers are flushed, no body should be written.outputMessage.flush();ShallowEtagHeaderFilter.disableContentCaching(inputMessage.getServletRequest());// Skip call to converters, as they may update the body.return;}}else if (returnStatus / 100 == 3) {String location = outputHeaders.getFirst("location");if (location != null) {saveFlashAttributes(mavContainer, webRequest, location);}}}// Try even with null body. ResponseBodyAdvice could get involved.writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);// Ensure headers are flushed even if no body was written.outputMessage.flush();
}

分开来看下:

mavContainer.setRequestHandled(true);
if (returnValue == null) {return;
}

这段代码是为了标识该请求已经被处理过,不需要走view渲染逻辑。

HttpHeaders outputHeaders = outputMessage.getHeaders();
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty()) {entityHeaders.forEach((key, value) -> {if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) {List<String> values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders);if (!values.isEmpty()) {outputHeaders.setVary(values);}}else {outputHeaders.put(key, value);}});
}

这段代码处理ResponseEntity里的header部分,写入httpResponse对象的header里。

接下来的代码负责将entity中的status也写入httpResponse对象中。

最后body部分是由这段代码处理的:

// Try even with null body. ResponseBodyAdvice could get involved.
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);

调用了父类的模板方法里,该模板方法负责将内部的messageConverter实例一次调用,根据mediaType找出合适的:

for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);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;}
}

这部分内容就不深入了,在另一篇文章中会介绍。

小结:

ResponseEntity对应一个http请求或者响应,可以用在controller的返回值里,方便处理header及status。

ResponseEntity类型的返回值由一个特殊的HttpEntityMethodProcessor类型的returnTypeHandler来处理,主要是将ResponseEntity里设置的header和status写入到httpResponse中,body部分调用的是父类的模板方法。

【SpringMVC(十三)】ResponseEntity 使用 及 原理相关推荐

  1. SpringMVC源码分析_1 SpringMVC容器启动和加载原理

                                                                    SpringMVC源码分析_1 SpringMVC启动和加载原理     ...

  2. SpringMVC基于ResponseEntity的文件下载

    SpringMVC要求我们返回一个视图,否则会抛出异常 而@ResponseBody使Controller直接返回JSON数据. 而ResponseEntity 可以定义返回的HttpHeaders和 ...

  3. springmvc工作流程_SpringMVC工作原理

    买了好多书,但是没有一本是看完的,这是看完的第一本书,虽然页数不多.技术早就用了老多遍了,还是总结一下吧! 一.MVC模式 MVC是 model.view.和controller的缩写,分别代表web ...

  4. springmvc工作流程_springMVC工作原理及流程详细讲解

    简述 本文主要介绍springMVC工作原理. 工作原理 客户端发送HTTP请求,DispatcherServlet控制器拦截到请求,调用HandlerMapping 解析请求对应的Handler,H ...

  5. SpringMVC执行流程及工作原理

    1.SpringMVC的原理和组成 从上图中可以看出:SpringMVC是属于SpringWeb里面的一个功能模块(SpringWebMVC).专门用来开发SpringWeb项目的一种MVC模式的技术 ...

  6. springmvc十三:REST风格增删改查

    如果从页面发起PUT,DELETE请求?  spring提供了对rest风格的支持. 1). SpringMVC中有一个Filter,他可以把普通的请求转换为规定形式的请求,要配置一个filter & ...

  7. html页面源码_整合SpringMVC之错误处理底层原理及源码分析

    一. SpringBoot的默认错误处理策略 1. 对404的默认处理策略 我们在发送请求的时候,如果发生了404异常,SpringBoot是怎么处理的呢? 我们可以随便发送一个不存在的请求来验证一下 ...

  8. [网络安全自学篇] 十三.Wireshark抓包原理(ARP劫持、MAC泛洪)及数据流追踪和图像抓取(二)

    这是作者的系列网络安全自学教程,主要是关于网安工具和实践操作的在线笔记,特分享出来与博友共勉,希望您们喜欢,一起进步.前文分享了Wireshark安装入门和一个抓取网站用户名和密码的案例,本篇文章将继 ...

  9. 单片机小白学步系列(二十三) IO口原理知识补充:双向IO口、互补推挽、高阻态

    由于之前考虑不周,本篇在IO口原理知识的基础上,进一步补充一些知识. ================================================= 双向IO口的输出:互补推挽 在 ...

  10. springMVC 源码级别总结原理,DispatcherServlet核心方法

    前言 springMVC自我总结 本次maven: <!-- https://mvnrepository.com/artifact/org.springframework/spring-webm ...

最新文章

  1. 深度学习Pytorch框架Tensor张量
  2. 白话异常检测算法Isolation Forest
  3. 2022还在使用Mysql进行数据检索?ElasticSearch自定义扩展词库完成检索
  4. h2 mysql 兼容_H2内存数据库对sql语句的支持问题 sql放到mysql数据库中能跑
  5. 儿童编程python入门_儿童编程python入门
  6. Agile PLM EC Understand the BOM Publishing Process
  7. SpringCloud系列十三:Feign对继承、压缩、日志的支持以及构造多参数请求
  8. python生成字符画_通过python将图片生成字符画
  9. python测试脚本 进制转换_[python] 转换python脚本程序为二进制ELF
  10. python中的深拷贝和浅拷贝
  11. 如何查看linux 版本
  12. html 设置表格打印宽度设置,html表格怎么设置宽度
  13. JetBrains推出体验版Fleet神器
  14. 基于51单片机的红外计数器proteus仿真 LCD1602显示原理图程序设计
  15. 小三角箭头向下向上查看隐藏的效果 vue
  16. 一键实现证件照背景的替换,Python 制作可视化GUI界面真香啊
  17. openssl 从内存直接加载CA证书
  18. DSL 领域特定语言
  19. Vue h5 调用微信扫码接口
  20. model.predict_classes(test) 和model.predict(test) 区别

热门文章

  1. stm32f407Zgt6 与 hc05蓝牙模块通信
  2. 【37期】请你详细说说类加载流程,类加载机制及自定义类加载器
  3. 获取windows用户密码——Jhon
  4. 阿里云ECS节省计划重磅发布 比包年包月灵活,比按量付费划算,最高节省76%费用
  5. Exiting because of unfinished merge.
  6. Ubuntu中文件颜色的含义
  7. 根据旋转矩阵计算角度差
  8. eNSP实验vlan及交换机接口类型配置
  9. 【c++】cout.setf(left)、cout.setf(right)详解
  10. 相似度系列-5:语义方法:BERTSCORE: EVALUATING TEXT GENERATION WITH BERT