Try Spring Boot 2
Tags: java, springboot
Published: 2023-11-23 12:38:54

## Intro
In this time, I will try uploading files and "CompletableFuture" to execute operations asynchronously.

* [Try Spring Boot](https://dev.to/masanori_msl/try-springboot-lg0)

## Uploading files

#### index.page.ts
```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
```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);
    }
}
```

* [Getting Started|Uploading Files](https://spring.io/guides/gs/uploading-files/)

## 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
```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
```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](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletableFuture.html)
* [Executors - Java SE 21 & JDK 21](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Executors.html)
* [Runnable - Java SE 21 & JDK 21](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Runnable.html)

### 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
```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".

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/treo6hyz0hzdi51u4355.png)


So I change from "runAsync" to "supplyAsync".

#### [Callable] SpreadsheetFileService.java
```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();
    }
}
```
* [Supplier - Java SE 21 & JDK 21](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Supplier.html)

#### Result
```
Start generating
Hello Callable
Hello Callable2
OK: Hello World!
End  generating
```

Supplier<T> interface only has "get()" method like Callable<T>.

### When threads will be changed?
I try checking threads by their names.

#### SpringbootsampleApplication.java
```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
```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
```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
```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
```