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

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more