【SpringMVC(十三)】ResponseEntity 使用 及 原理

32 篇文章 15 订阅
30 篇文章 4 订阅

使用

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;

	@Nullable
	private 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中:
 

	@Override
	public 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部分调用的是父类的模板方法。

  • 20
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC提供了一个HttpEntity类,用于封装HTTP请求和响应的实体。 HttpEntity是一个泛型类,可以用于封装各种类型的请求和响应体。它包含了HTTP头信息以及请求/响应的主体内容。 在Spring MVC中,我们可以将参数类型声明为HttpEntity,来接收和处理HTTP请求的实体信息。例如,我们可以将请求体中的JSON数据映射到一个Java对象中: ```java @RequestMapping(value = "/user", method = RequestMethod.POST) public ResponseEntity<String> createUser(@RequestBody HttpEntity<User> requestEntity) { User user = requestEntity.getBody(); // 处理user对象 // ... return new ResponseEntity<>("User created", HttpStatus.CREATED); } ``` 在上述例子中,我们使用了`HttpEntity<User>`作为参数类型来接收请求体中的JSON数据,并将其映射到一个User对象中。我们可以通过调用`getBody()`方法来获取请求体内容。 同样地,我们也可以在响应中使用HttpEntity来封装响应的内容。例如,我们可以返回一个包含JSON数据的HttpEntity对象: ```java @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public HttpEntity<User> getUser(@PathVariable("id") int id) { User user = userService.getUserById(id); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return new HttpEntity<>(user, headers); } ``` 在上述例子中,我们使用了`HttpEntity<User>`作为返回类型,并将User对象及自定义的HTTP头信息封装在其中。 总结而言,Spring MVC的HttpEntity类提供了一种方便的方式来封装和处理HTTP请求和响应的实体信息。我们可以使用它来接收和处理请求体中的内容,并将响应数据封装成一个HttpEntity对象返回给客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值