Web Framework
Sesetengah web framework boleh memanfaatkan CompletableFuture
, antaranya
Spring Boot dan Play Framework. Dalam tutorial ini, kita akan melihat contoh
menggunakan CompletableFuture
dalam Spring Boot.
Katakanlah kita ada servis UserService
seperti berikut,
@Service
public class UserService {
public CompletionStage<String> getName() {
return "Muhammad Ali";
}
public CompletionStage<String> getFacebookImageUrl() {
return "https://www.example.com/ali.jpg";
}
}
Anggap kedua-dua method tersebut mengambil data dari database.
Kemudian controller untuk maklumat user, UserApiController
, seperti berikut,
@RestController
public class UserApiController {
@Autowired
private UserService userService;
@GetMapping("/user")
public String userInfo() {
return userService.getName() + ", " + userService.getFacebookImageUrl();
}
}
CompletionStage
Sekarang kita akan menggantikan code di atas dengan CompletableFuture
. Caranya
adalah dengan menjadikan method supaya return CompletionStage
atau
CompletableFuture
.
CompletionStage
ialah interface kepadaCompletableFuture
.
Contoh untuk UserService
,
public CompletionStage<String> getName() {
return supplyAsync(() -> "Muhammad Ali");
}
public CompletionStage<String> getFacebookImageUrl() {
return supplyAsync(() -> "https://www.example.com/ali.jpg");
}
Kemudian kita gabungkan di controller, seperti berikut,
@GetMapping("/user")
public CompletionStage<String> userInfo() {
return userService.getName().thenCombineAsync(
userService.getFacebookImageUrl(),
(name, fbImgUrl) -> name + ", " + fbImgUrl
);
}
Spring Boot secara automatik akan tahu yang kita sedang menulis code
asynchronous dan akan handle dengan betul. Jadi, kita tidak perlu menggunakan
thenAccept
atau join
untuk mendapatkan output untuk return dari controller.
Executor
Dalam Tutorial Executor yang lepas, kita faham ada kerenah dengan
program apabila kita menggunakan lebih daripada satu thread, iaitu apabila
program perlu ditutup. Jika kita ingin menggunakan ExecutorService
dalam
sesuatu framework, kita perlu mengambil tahu bagaimana lifecycle(kitar hidup)
framework tersebut, kemudian research bagaimana untuk menjalankan sesuatu proses
pada fasa tertentu.
Selepas membuat sedikit research (cari di google), kita dapat tahu yang kita
boleh tetapkan method yang perlu dipanggil apabila sesuatu Spring Bean ditutup
menggunakan parameter destroyMethod
.
Sekarang kita cuba create ExecutorService
untuk database dan logger dalam
config,
@Configuration
public class GlobalConfig {
@Bean(destroyMethod = "shutdown")
@Qualifier("db")
public ExecutorService dbThreadPool() {
return Executors.newFixedThreadPool(100);
}
@Bean(destroyMethod = "shutdown")
@Qualifier("logger")
public ExecutorService loggerThreadPool() {
return Executors.newSingleThreadExecutor();
}
}
Seperti code di atas, kita letakkan nama method yang perlu dipanggil, iaitu
shutdown
untuk parameter destroyMethod
. Kita juga menggunakan annotation
Qualifier
untuk membezakan ExecutorService
mana yang kita mahu semasa
membuat autowire.
Setelah selesai, maka kita boleh menggunakan thread pool tersebut pada code
async yang bersesuaian. Katakanlah dbThreadPool
untuk proses database, dan
loggerThreadPool
untuk melihat output yang kita beri kepada pengguna website.
Code untuk UserService
,
@Service
public class UserService {
@Autowired
@Qualifier("db")
private ExecutorService dbThreadPool;
public CompletionStage<String> getName() {
return supplyAsync(() -> "Muhammad Ali", dbThreadPool);
}
public CompletionStage<String> getFacebookImageUrl() {
return supplyAsync(() -> "https://www.example.com/ali.jpg", dbThreadPool);
}
}
Code untuk UserApiController
,
@RestController
public class UserApiController {
@Autowired
@Qualifier("logger")
private ExecutorService loggerThreadPool;
@Autowired
private UserService userService;
@GetMapping("/user")
public CompletionStage<String> userInfo() {
CompletionStage<String> userInfo = userService.getName()
.thenCombineAsync(
userService.getFacebookImageUrl(),
(name, fbImgUrl) -> name + ", " + fbImgUrl
);
userInfo.thenAcceptAsync(System.out::println, loggerThreadPool);
return userInfo;
}
}
Jika kita pergi ke page website tersebut, program akan output ke console pada masa yang sama.
@Async
Satu lagi cara adalah dengan menggunakan annotation @Async
yang disediakan
oleh Spring Framework. Untuk menggunakan @Async
, kita perlu enable dahulu
menggunakan @EnableAsync
. Jadi, tambah annotation @EnableAsync
pada class
GlobalConfig
,
@Configuration
@EnableAsync
public class GlobalConfig {
@Bean(destroyMethod = "shutdown")
@Qualifier("db")
public ExecutorService dbThreadPool() ...
@Bean(destroyMethod = "shutdown")
@Qualifier("logger")
public ExecutorService loggerThreadPool() ...
}
Sekarang kita boleh mengubah code untuk getName
dan getFacebookImageUrl
seperti berikut,
@Async("db")
public CompletableFuture<String> getName() {
return completedFuture("Muhammad Ali");
}
@Async("db")
public CompletableFuture<String> getFacebookImageUrl() {
return completedFuture("https://www.example.com/ali.jpg");
}
Code dalam method yang diletakkan @Async
akan diproses dalam thread yang lain
walaupun kita tidak menggunakan method supplyAsync
. Kita meletakkan "db"
supaya code tersebut menggunakan thread pool untuk database. Jika kita tidak
letak apa-apa argument, Spring akan menggunakan thread pool default. Akhir
sekali, method tersebut perlu return CompletableFuture
, bukan
CompletionStage
.