如何在 RestController 中缓存 InputStreamResource?

How to cache a InputStreamResource In RestController?

我有一个 servlet,returns 图像为 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)

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

缓存管理器会将 InputStreamResource 添加到缓存 ResponseEntity 中。第一次会没事的。但是当缓存 ResponseEntity 将尝试读取 InputStreamResouce 第二次时你会得到异常,因为它无法多次读取流。

解决方法:不缓存InputStreamResouce本身,而是缓存stream的内容。

@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);
    }
}

我使用了 org.apache.commons.io 中的 IOUtils.read() 来将字节从流复制到数组,但您可以通过任何首选方式来完成。

您无法缓存流。一旦他们被阅读,他们就消失了。 错误消息非常清楚:

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

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

如果是这种情况,您最好的选择是将文件的内容读取到 ByteArray,然后 cache/return 改为读取文件内容。