【SpringMVC(十三)】ResponseEntity 使用 及 原理
使用
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 使用 及 原理相关推荐
- SpringMVC源码分析_1 SpringMVC容器启动和加载原理
SpringMVC源码分析_1 SpringMVC启动和加载原理 ...
- SpringMVC基于ResponseEntity的文件下载
SpringMVC要求我们返回一个视图,否则会抛出异常 而@ResponseBody使Controller直接返回JSON数据. 而ResponseEntity 可以定义返回的HttpHeaders和 ...
- springmvc工作流程_SpringMVC工作原理
买了好多书,但是没有一本是看完的,这是看完的第一本书,虽然页数不多.技术早就用了老多遍了,还是总结一下吧! 一.MVC模式 MVC是 model.view.和controller的缩写,分别代表web ...
- springmvc工作流程_springMVC工作原理及流程详细讲解
简述 本文主要介绍springMVC工作原理. 工作原理 客户端发送HTTP请求,DispatcherServlet控制器拦截到请求,调用HandlerMapping 解析请求对应的Handler,H ...
- SpringMVC执行流程及工作原理
1.SpringMVC的原理和组成 从上图中可以看出:SpringMVC是属于SpringWeb里面的一个功能模块(SpringWebMVC).专门用来开发SpringWeb项目的一种MVC模式的技术 ...
- springmvc十三:REST风格增删改查
如果从页面发起PUT,DELETE请求? spring提供了对rest风格的支持. 1). SpringMVC中有一个Filter,他可以把普通的请求转换为规定形式的请求,要配置一个filter & ...
- html页面源码_整合SpringMVC之错误处理底层原理及源码分析
一. SpringBoot的默认错误处理策略 1. 对404的默认处理策略 我们在发送请求的时候,如果发生了404异常,SpringBoot是怎么处理的呢? 我们可以随便发送一个不存在的请求来验证一下 ...
- [网络安全自学篇] 十三.Wireshark抓包原理(ARP劫持、MAC泛洪)及数据流追踪和图像抓取(二)
这是作者的系列网络安全自学教程,主要是关于网安工具和实践操作的在线笔记,特分享出来与博友共勉,希望您们喜欢,一起进步.前文分享了Wireshark安装入门和一个抓取网站用户名和密码的案例,本篇文章将继 ...
- 单片机小白学步系列(二十三) IO口原理知识补充:双向IO口、互补推挽、高阻态
由于之前考虑不周,本篇在IO口原理知识的基础上,进一步补充一些知识. ================================================= 双向IO口的输出:互补推挽 在 ...
- springMVC 源码级别总结原理,DispatcherServlet核心方法
前言 springMVC自我总结 本次maven: <!-- https://mvnrepository.com/artifact/org.springframework/spring-webm ...
最新文章
- 深度学习Pytorch框架Tensor张量
- 白话异常检测算法Isolation Forest
- 2022还在使用Mysql进行数据检索?ElasticSearch自定义扩展词库完成检索
- h2 mysql 兼容_H2内存数据库对sql语句的支持问题 sql放到mysql数据库中能跑
- 儿童编程python入门_儿童编程python入门
- Agile PLM EC Understand the BOM Publishing Process
- SpringCloud系列十三:Feign对继承、压缩、日志的支持以及构造多参数请求
- python生成字符画_通过python将图片生成字符画
- python测试脚本 进制转换_[python] 转换python脚本程序为二进制ELF
- python中的深拷贝和浅拷贝
- 如何查看linux 版本
- html 设置表格打印宽度设置,html表格怎么设置宽度
- JetBrains推出体验版Fleet神器
- 基于51单片机的红外计数器proteus仿真 LCD1602显示原理图程序设计
- 小三角箭头向下向上查看隐藏的效果 vue
- 一键实现证件照背景的替换,Python 制作可视化GUI界面真香啊
- openssl 从内存直接加载CA证书
- DSL 领域特定语言
- Vue h5 调用微信扫码接口
- model.predict_classes(test) 和model.predict(test) 区别
热门文章
- stm32f407Zgt6 与 hc05蓝牙模块通信
- 【37期】请你详细说说类加载流程,类加载机制及自定义类加载器
- 获取windows用户密码——Jhon
- 阿里云ECS节省计划重磅发布 比包年包月灵活,比按量付费划算,最高节省76%费用
- Exiting because of unfinished merge.
- Ubuntu中文件颜色的含义
- 根据旋转矩阵计算角度差
- eNSP实验vlan及交换机接口类型配置
- 【c++】cout.setf(left)、cout.setf(right)详解
- 相似度系列-5:语义方法:BERTSCORE: EVALUATING TEXT GENERATION WITH BERT