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)