I'm trying to upload file and then read it, everything is working fine, but not when I put @Async
annotation on the handler method.
I don't want the user to keep waiting until it processes the file. But after putting this annotation I get java.lang.IllegalStateException: File has been moved - cannot be read again
exception. What happens and how do I fix this? As I understand, Spring could be just clearing the file because request-response ends and it cleans it up. But shouldn't @Async
prevent this?
Sample Spring Boot application:
@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();
}
}
Upload controller:
@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);
}
}
Upload service:
@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)));
}
}
And now I get java.io.FileNotFoundException
. I'm not sure what I'm missing here. Probably I'm doing something wrong as I couldn't find any error on this and I'd think this is very common use-case.
You cannot hand over the MultipartFile paramter to your @Async
Method in the way you do it.
When the addSource
method ends, the MultipartFile
runs out of scope and the resource is released. So the access inside your "process" method will fail. You build some kind of a race-condition this way.
Springs DispatcherServlet
uses StandardServletMultipartResolver.cleanupMultipart
to cleanup those files. Put a breakpoint there to see when this method gets called upon retun of addSource(...)
.
You should do the following: Read the entire file into a Buffer inside the addSource
method. Then pass the buffer to the process
Method an let addSource
return.
What you call "...processes the file..." is not "processing" the file, but reading it.
@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));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With