Intro
In this time, I will try uploading files and "CompletableFuture" to execute operations asynchronously.
Uploading files
index.page.ts
... sendFile() { const fileInput = document.getElementById("selected_file_input") as HTMLInputElement; if (fileInput?.files == null || fileInput.files.length <= 0) { alert("No file data"); return; } const file = fileInput.files[0]!; const reader = new FileReader(); reader.onload = () => { const data = reader.result; if (data == null || typeof (data) === "string") { alert("Invalid data type"); return; } const formData = new FormData(); formData.append("file", new Blob([data])); fetch("http://localhost:8080/files", { mode: "cors", method: "POST", headers: { "Content-Type": file.type }, body: formData }) .then(res => res.json()) .then(res => console.log(res)) .catch(err => console.error(err)); } reader.readAsArrayBuffer(file); } }
FileController.java
package jp.masanori.springbootsample.files; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; import jp.masanori.springbootsample.apps.ActionResult; @RestController public class FileController { private final FileService files; public FileController(FileService files) { this.files = files; } @PostMapping("/files") public ActionResult uploadFile(HttpServletRequest request, @RequestBody byte[] file) { return files.startGenerating(request.getHeader("File-Name"), request.getContentType(), file); } }
Asynchronous
After uploading a file, I would like to read it and edit it.
I want to do that asynchronously since it may take a long time.
No returning values
To wait until operations complete, I can use "CompletableFuture".
[Runnable] SpreadsheetEditor.java
package jp.masanori.springbootsample.files; public class SpreadsheetEditor implements Runnable { public void run() { try { System.out.println("Hello Runnable"); Thread.sleep(5 * 1000); System.out.println("Hello Runnable2"); } catch (InterruptedException e) { System.out.println(e.getMessage()); } } }
[Runnable] SpreadsheetFileService.java
package jp.masanori.springbootsample.files; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import org.springframework.stereotype.Service; import jp.masanori.springbootsample.apps.ActionResult; @Service public class SpreadsheetFileService implements FileService { public ActionResult startGenerating(String fileName, String contentType, byte[] file) { System.out.println("Start generating"); try { // The operation of SpreadsheetEditor starts from this line. // Use cached thread if the application holds any cached thread CompletableFuture<Void> task = CompletableFuture.runAsync(new SpreadsheetEditor(), Executors.newCachedThreadPool()); // Wait until the operation complete task.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("End generating"); return ActionResult.getSucceededResult(); } }
Result
Start generating Hello Runnable Hello Runnable2 End generating
When I don't need wait for completion, I can remove "take.get()".
- CompletableFuture - Java SE 21 & JDK 21
- Executors - Java SE 21 & JDK 21
- Runnable - Java SE 21 & JDK 21
Receiving processing result values
Classes implementing "Runnable" can't return values.
When I want do so, I should change from "Runnable" to "Callable".
[Callable] SpreadsheetEditor.java
... import java.util.Optional; import java.util.concurrent.Callable; public class SpreadsheetEditor implements Callable<Optional<String>> { public Optional<String> call() { try { System.out.println("Hello Callable"); Thread.sleep(5 * 1000); System.out.println("Hello Callable2"); return Optional.of("Hello World!"); } catch (InterruptedException e) { e.printStackTrace(); } return Optional.empty(); } }
Now I have a problem.
"Callable" can't be set as an argument to the "CompletableFuture.runAsync".
So I change from "runAsync" to "supplyAsync".
[Callable] SpreadsheetFileService.java
... @Service public class SpreadsheetFileService implements FileService { public ActionResult startGenerating(String fileName, String contentType, byte[] file) { System.out.println("Start generating"); CompletableFuture<Optional<String>> task = CompletableFuture.supplyAsync( () -> new SpreadsheetEditor().call(), Executors.newCachedThreadPool()); try { Optional<String> result = task.get(); if (result.isPresent()) { System.out.println("OK: " + result.get()); } else { System.out.println("NG"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("End generating"); return ActionResult.getSucceededResult(); } }
Result
Start generating Hello Callable Hello Callable2 OK: Hello World! End generating
Supplier interface only has "get()" method like Callable.
When threads will be changed?
I try checking threads by their names.
SpringbootsampleApplication.java
package jp.masanori.springbootsample; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringbootsampleApplication { public static void main(String[] args) { System.out.println("SpringbootsampleApplication.main1:" + Thread.currentThread().getName()); SpringApplication.run(SpringbootsampleApplication.class, args); System.out.println("SpringbootsampleApplication.main2:" + Thread.currentThread().getName()); } }
FileController.java
package jp.masanori.springbootsample.files; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; import jp.masanori.springbootsample.apps.ActionResult; @RestController public class FileController { private final FileService files; public FileController(FileService files) { this.files = files; } @PostMapping("/files") public ActionResult uploadFile(HttpServletRequest request, @RequestBody byte[] file) { System.out.println("FileController.uploadFile1:" + Thread.currentThread().getName()); ActionResult result = files.startGenerating(request.getHeader("File-Name"), request.getContentType(), file); System.out.println("FileController.uploadFile2:" + Thread.currentThread().getName()); return result; } }
SpreadsheetFileService.java
... @Service public class SpreadsheetFileService implements FileService { public ActionResult startGenerating(String fileName, String contentType, byte[] file) { System.out.println("SpreadsheetFileService.startGenerating1:" + Thread.currentThread().getName()); CompletableFuture<Optional<String>> task = CompletableFuture.supplyAsync( () -> { System.out.println("SpreadsheetFileService.startGenerating2:" + Thread.currentThread().getName()); return new SpreadsheetEditor().call(); }, Executors.newCachedThreadPool()); try { System.out.println("SpreadsheetFileService.startGenerating3:" + Thread.currentThread().getName()); Optional<String> result = task.get(); if (result.isPresent()) { System.out.println("OK: " + result.get()); } else { System.out.println("NG"); } System.out.println("SpreadsheetFileService.startGenerating4:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("End generating"); return ActionResult.getSucceededResult(); } }
SpreadsheetEditor.java
... public class SpreadsheetEditor implements Callable<Optional<String>> { public Optional<String> call() { try { System.out.println("SpreadsheetEditor.call1:" + Thread.currentThread().getName()); Thread.sleep(5 * 1000); System.out.println("SpreadsheetEditor.call2:" + Thread.currentThread().getName()); return Optional.of("Hello World!"); } catch (InterruptedException e) { e.printStackTrace(); } return Optional.empty(); } }
Result
SpringbootsampleApplication.main1:main SpringbootsampleApplication.main1:restartedMain SpringbootsampleApplication.main2:restartedMain FileController.uploadFile1:http-nio-8080-exec-1 SpreadsheetFileService.startGenerating1:http-nio-8080-exec-1 SpreadsheetFileService.startGenerating3:http-nio-8080-exec-1 SpreadsheetFileService.startGenerating2:pool-2-thread-1 SpreadsheetEditor.call1:pool-2-thread-1 SpreadsheetEditor.call2:pool-2-thread-1 OK: Hello World! SpreadsheetFileService.startGenerating4:http-nio-8080-exec-1 End generating FileController.uploadFile2:http-nio-8080-exec-1
Top comments (0)