如何在RestController中缓存InputStreamResource?

时间:2016-06-23 07:11:08

标签: spring spring-mvc resources inputstream illegalstateexception

我有一个servlet,它返回一个InputStreamResource的图像。根据一些get query参数,大约有50个静态图像要返回。

因为每次请求时都不必查看每个图像(通常是这样),我想缓存这些图像的响应。

@RestController
public class MyRestController {
    //code is just example; may be any number of parameters
    @RequestMapping("/{code}")
    @Cachable("code.cache")
    public ResponseEntity<InputStreamResource> getCodeLogo(@PathVariable("code") String code) {
        FileSystemResource file = new FileSystemResource("d:/images/" + code + ".jpg");
        return ResponseEntity.ok()
            .contentType("image/jpg")
            .lastModified(file.lastModified())
            .contentLength(file.contentLength())
            .body(new InputStreamResource(file.getInputStream()));

    }
}

使用@Cacheable注释时(无论是直接在RestMapping方法上还是重构为外部服务),我都会遇到以下异常:

cause: java.lang.IllegalStateException: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times - error: InputStream has already been read - do not use InputStreamResource if a stream needs to be read multiple times
org.springframework.core.io.InputStreamResource.getInputStream(InputStreamResource.java:96)
org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:100)
org.springframework.http.converter.ResourceHttpMessageConverter.writeInternal(ResourceHttpMessageConverter.java:47)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:195)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:238)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)

问题:我怎样才能缓存ResponseEntity类型的InputStreamResource

2 个答案:

答案 0 :(得分:6)

缓存管理器将添加到缓存ResponseEntity,其中包含InputStreamResource。第一次没问题。但是,当缓存ResponseEntity第二次尝试读取InputStreamResouce时,您将获得异常,因为它无法多次读取流。

解决方案:不要缓存InputStreamResouce本身,而是缓存流的内容。

@RestController
public class MyRestController {    
    @RequestMapping("/{code}")
    @Cachable("code.cache")
    public ResponseEntity<byte[]> getCodeLogo(@PathVariable("code") String code) {
        FileSystemResource file = new FileSystemResource("d:/images/" + code + ".jpg");

        byte [] content = new byte[(int)file.contentLength()];
        IOUtils.read(file.getInputStream(), content);

        return ResponseEntity.ok()
            .contentType(MediaType.IMAGE_JPEG)
            .lastModified(file.lastModified())
            .contentLength(file.contentLength())
            .body(content);
    }
}

我已使用IOUtils.read()中的org.apache.commons.io将字节从流复制到数组,但您可以通过任何首选方式执行此操作。

答案 1 :(得分:2)

您无法缓存Streams。一旦阅读完毕,它们便消失了。 错误信息非常清楚:

InputStream has already been read -
do not use InputStreamResource if a stream needs to be read multiple times

根据您的代码和评论,在我看来,您有一个带有JPG徽标的大images文件夹(可能会添加,删除或修改),并且您希望每天都有 你要求的人的缓存,所以你不必经常从磁盘重新加载它们。

如果是这种情况,您最好的选择是将文件的内容读取到ByteArray并缓存/返回。