使用RestTemplate获取InputStream

我正在使用URL类从中读取InputStream。 我有什么方法可以使用RestTemplate吗?

InputStream input = new URL(url).openStream(); JsonReader reader = new JsonReader(new InputStreamReader(input, StandardCharsets.UTF_8.displayName())); 

如何使用RestTemplate而不是使用URL获取InputStream

你不应该。 RestTemplate用于封装处理响应(和请求)内容。

相反,您可以注册适当的HttpMessageConverter对象。 那些人可以通过HttpInputMessage对象访问响应的InputStream

正如Abdull建议的那样 ,Spring确实为Resource提供了一个HttpMessageConverter实现,它本身包含了一个InputStreamResourceHttpMessageConverter 。 它不支持所有Resource类型,但由于您应该编程接口,所以您应该只使用超接口Resource

当前实现(4.3.5)将返回一个ByteArrayResource ,其中响应流的内容被复制到您可以访问的新ByteArrayInputStream中。

您不必关闭流。 RestTemplate会为您完成这项工作。 (如果您尝试使用InputStreamResource ,这是另一种类型,这是ResourceHttpMessageConverter支持的,这是很不幸的,因为它包装了底层响应的InputStream但在它暴露给您的客户端代码之前InputStream关闭。)

Spring有一个org.springframework.http.converter.ResourceHttpMessageConverter 。 它转换Spring的org.springframework.core.io.Resource类。 该Resource类封装了一个InputStream ,您可以通过someResource.getInputStream()获取它。

把这一切放在一起,你可以通过指定Resource.class作为RestTemplate调用的响应类型,通过RestTemplate实际获得一个InputStream

以下是使用RestTemplateexchange(..)方法之一的示例:

 import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpMethod; import org.springframework.core.io.Resource; ResponseEntity responseEntity = restTemplate.exchange( someUrlString, HttpMethod.GET, someHttpEntity, Resource.class ); InputStream responseInputStream; try { responseInputStream = responseEntity.getBody().getInputStream(); } catch (IOException e) { throw new RuntimeException(e); } // use responseInputStream 

以前的答案并没有错,但它们并没有深入到我喜欢的深度。 有些情况下,处理低级别InputStream不仅是理想的,而且是必要的,最常见的例子是将大型文件从源(某些Web服务器)流式传输到目标(数据库)。 如果您尝试使用ByteArrayInputStream ,那么您将会遇到OutOfMemoryError ,并不会令人惊讶。 是的,您可以滚动自己的HTTP客户端代码,但是您必须处理错误的响应代码,响应转换器等。如果您已经在使用Spring,那么寻找RestTemplate是一个自然的选择。

在撰写本文时, spring-web:5.0.2.RELEASE有一个ResourceHttpMessageConverter ,它有一个boolean supportsReadStreaming ,如果设置,响应类型为InputStreamResource ,则返回InputStreamResource ; 否则返回ByteArrayResource 。 很明显,你并不是唯一一个要求流媒体支持的人。

但是,有一个问题: RestTemplateHttpMessageConverter运行后很快关闭响应。 因此,即使您要求输入InputStreamResource并获得它,也没有用,因为响应流已经关闭。 我认为这是他们忽视的设计缺陷; 它应该依赖于响应类型。 所以不幸的是,对于阅读,你必须完全消费反应; 如果使用RestTemplate则无法传递它。

写作没有问题。 如果要流式传输InputStreamResourceHttpMessageConverter将为您完成。 在引擎盖下,它使用org.springframework.util.StreamUtils一次从InputStreamOutputStream写入4096个字节。

一些HttpMessageConverter支持所有媒体类型,因此根据您的要求,您可能必须从RestTemplate删除默认RestTemplate ,并设置您需要的那些,同时注意它们的相对排序。

最后但并非最不重要的是, ClientHttpRequestFactory实现有一个boolean bufferRequestBody ,如果要上传boolean bufferRequestBody ,则可以并且应该设置为false 。 否则,你知道, OutOfMemoryError 。 在撰写本文时, SimpleClientHttpRequestFactory (JDK客户端)和HttpComponentsClientHttpRequestFactory (Apache HTTP客户端)支持此function,但不支持OkHttp3ClientHttpRequestFactory 。 再次,设计监督。

编辑 :提交的票据SPR-16885 。

作为变体,您可以将响应作为字节消耗,而不是转换为流

 byte data[] = restTemplate.execute(link, HttpMethod.GET, null, new BinaryFileExtractor()); return new ByteArrayInputStream(data); 

提取器是

 public class BinaryFileExtractor implements ResponseExtractor { @Override public byte[] extractData(ClientHttpResponse response) throws IOException { return ByteStreams.toByteArray(response.getBody()); } } 

感谢Abhijit Sarkar的领导方式。

我需要下载一个繁重的JSON流并将其分解为可流动的小型可管理数据。 JSON由具有大属性的对象组成:这些大属性可以序列化为文件,因此可以从解组的JSON对象中删除。

另一个用例是按对象下载JSON流对象,像map / reduce algorythm一样处理它,并生成单个输出,而不必将整个流加载到内存中。

另一个用例是读取一个大的JSON文件,只根据条件选择一些对象,同时解组为Plain Old Java Objects。

这是一个例子:我们想要流一个非常庞大的JSON文件,它是一个数组,我们只想检索数组中的第一个对象。

鉴于服务器上的这个大文件,请访问http://example.org/testings.json :

 [ { "property1": "value1", "property2": "value2", "property3": "value3" }, { "property1": "value1", "property2": "value2", "property3": "value3" }, ... 1446481 objects => a file of 104 MB => take quite long to download... ] 

可以将此JSON数组的每一行解析为此对象:

 @lombok.Data public class Testing { String property1; String property2; String property3; } 

您需要此类使解析代码可重用:

 import com.fasterxml.jackson.core.JsonParser; import java.io.IOException; @FunctionalInterface public interface JsonStreamer { /** * Parse the given JSON stream, process it, and optionally return an object.
* The returned object can represent a downsized parsed version of the stream, or the result of a map/reduce processing, or null... * * @param jsonParser the parser to use while streaming JSON for processing * @return the optional result of the process (can be {@link Void} if processing returns nothing) * @throws IOException on streaming problem (you are also strongly encouraged to throw HttpMessageNotReadableException on parsing error) */ R stream(JsonParser jsonParser) throws IOException; }

而这个类要解析:

 import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import lombok.AllArgsConstructor; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import java.io.IOException; import java.util.Collections; import java.util.List; @AllArgsConstructor public class StreamingHttpMessageConverter implements HttpMessageConverter { private final JsonFactory factory; private final JsonStreamer jsonStreamer; @Override public boolean canRead(Class clazz, MediaType mediaType) { return MediaType.APPLICATION_JSON.isCompatibleWith(mediaType); } @Override public boolean canWrite(Class clazz, MediaType mediaType) { return false; // We only support reading from an InputStream } @Override public List getSupportedMediaTypes() { return Collections.singletonList(MediaType.APPLICATION_JSON); } @Override public R read(Class clazz, HttpInputMessage inputMessage) throws IOException { try (InputStream inputStream = inputMessage.getBody(); JsonParser parser = factory.createParser(inputStream)) { return jsonStreamer.stream(parser); } } @Override public void write(R result, MediaType contentType, HttpOutputMessage outputMessage) { throw new UnsupportedOperationException(); } } 

然后,这是用于流式传输HTTP响应的代码,解析JSON数组并仅返回第一个unmarshalled对象:

 // You should @Autowire these: JsonFactory jsonFactory = new JsonFactory(); ObjectMapper objectMapper = new ObjectMapper(); RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); // If detectRequestFactory true (default): HttpComponentsClientHttpRequestFactory will be used and it will consume the entire HTTP response, even if we close the stream early // If detectRequestFactory false: SimpleClientHttpRequestFactory will be used and it will close the connection as soon as we ask it to RestTemplate restTemplate = restTemplateBuilder.detectRequestFactory(false).messageConverters( new StreamingHttpMessageConverter<>(jsonFactory, jsonParser -> { // While you use a low-level JsonParser to not load everything in memory at once, // you can still profit from smaller object mapping with the ObjectMapper if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_ARRAY) { if (!jsonParser.isClosed() && jsonParser.nextToken() == JsonToken.START_OBJECT) { return objectMapper.readValue(jsonParser, Testing.class); } } return null; }) ).build(); final Testing firstTesting = restTemplate.getForObject("http://example.org/testings.json", Testing.class); log.debug("First testing object: {}", firstTesting);