Spring异步文件上传和处理

brg*_*rgs 11 spring spring-mvc

我正在尝试上传文件然后阅读它,一切正常,但是当我@Async在处理程序方法上添加注释时却没有.

我不希望用户在处理文件之前一直等待.但是在放入这个注释之后我得到了java.lang.IllegalStateException: File has been moved - cannot be read again例外.会发生什么,我该如何解决这个问题?据我所知,Spring可能只是清除文件,因为请求 - 响应结束并清理它.但是不应该@Async阻止这个?

示例Spring Boot应用程序:

@SpringBootApplication
@EnableSwagger2
@ComponentScan(value = "hello")
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.regex("/api/.*"))
                .build();
    }
}
Run Code Online (Sandbox Code Playgroud)

上传控制器:

@RestController
@RequestMapping(value = "/files")
public class FilesController {

    @Inject
    private Upload upload;

    @RequestMapping(method = RequestMethod.POST)
    public void addSource(@RequestParam MultipartFile file) throws IOException, InterruptedException {
        upload.process(file);
    }
}
Run Code Online (Sandbox Code Playgroud)

上传服务:

@Component
public class Upload {

    @Async
    public void process(MultipartFile file) throws InterruptedException, IOException {
        sleep(2000);
        System.out.println(new String(IOUtils.readFully(file.getInputStream(), -1, false)));
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我明白了java.io.FileNotFoundException.我不确定我在这里缺少什么.可能我做错了,因为我找不到任何错误,我认为这是非常常见的用例.

Tom*_*omB 17

您不能以您的方式将MultipartFile参数移交给@Async方法.

当"addSource"方法结束时,MultipartFile超出范围并释放资源.因此,"process"方法中的访问将失败.你通过这种方式构建某种竞赛条件.Springs DispatcherServlet使用StandardServletMultipartResolver.cleanupMultipart来清理这些文件.在那里放一个断点,看看何时在重新调用addSource(...)时调用此方法.

您应该执行以下操作:将整个文件读入"addSource"方法中的Buffer.然后将缓冲区传递给"进程"方法,让"addSource"返回.

你所谓的"...处理文件..."不是"处理"文件,而是阅读它.

@Async
public void process(byte[] bs){
    System.out.println(new String(bs));
    //do some long running processing of bs here
}

@RequestMapping(method = RequestMethod.POST)
public void addSource(@RequestParam MultipartFile file) {
    upload.process(IOUtils.toByteArray(file));
}
Run Code Online (Sandbox Code Playgroud)

  • 很好的解释!然而不幸的是,建议的解决方案存在一点点缺陷:在企业环境中,您经常会有大小不能缓冲到内存中的文件.可悲的是,`InputStreamResource`也不起作用.到目前为止,你最好的选择是将内容复制到自己的临时文件(例如`File#createTempFile`),你可以在处理线程的末尾清理它. (3认同)
  • 是的@Powerslave 你是绝对正确的。如果您负担不起内存中的缓冲,则应改用文件系统支持的缓冲区(也称为临时文件)。我在示例中使用内存方法,因为它不会产生文件处理中出现的新问题(访问限制、必要的清理任务等)。 (2认同)