DEV Community

Masui Masanori
Masui Masanori

Posted on • Edited on

Try Spring Boot 2

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

[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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Start generating
Hello Runnable
Hello Runnable2
End  generating
Enter fullscreen mode Exit fullscreen mode

When I don't need wait for completion, I can remove "take.get()".

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now I have a problem.
"Callable" can't be set as an argument to the "CompletableFuture.runAsync".

Image description

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Start generating
Hello Callable
Hello Callable2
OK: Hello World!
End  generating
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)