今天要介紹的是「CommandLineRunner」與「ApplicationRunner」,它們可以在 Spring Boot 應用程式啟動完成後,自動執行一些程式。在筆者進行了解後,發覺這是相對單純的功能。因此文末會分享一些拙見,提出可能可以在什麼情況下使用。
我們可以很容易地建立一個 CommandLineRunner。只要建立一個元件類別,並實作 CommandLineRunner
介面即可。
@Component
public class MyRunner implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void run(String... args) throws Exception {
logger.info("{} works!", getClass().getSimpleName());
}
}
實作 CommandLineRunner
介面,需要完成它的 run
方法。在此只是簡單地印出 log 文字。啟動後,便能在 console 看到 log。
由於是在 Spring Boot 啟動完成後,才會執行這個 runner,因此我們自己開發的其它元件(bean),都可注入進來使用。
另外,關於 run
方法的參數 args
,指的是啟動 JAR 檔所附加的參數選項。例如:
java -jar XXX.jar --spring.config.name=application --spring.config.location=./
在使用 CommandLineRunner 時要注意,如果中途發生了 exception,卻沒有做例外處理,那麼 Spring Boot 就會啟動失敗,程式中止。
以下是一個會故意拋出例外的 runner。
@Component
public class MyRunner implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void run(String... args) throws Exception {
// 計數 1 ~ 5
for (var i = 1; i <= 5; i++) {
logger.info("{} counts: {}", getClass().getSimpleName(), i);
// 數到 4 時拋出例外
if (i == 4) {
throw new RuntimeException("MyRunner fails...");
}
}
}
}
下圖是以上的 runner 執行到一半,發生例外而導致程式中止的 log。
所以讀者使用 CommandLineRunner 時,請評估是否有發生例外的可能性。如果有,而且有方案去因應這種情況,別忘了使用 try catch
將程式邏輯包起來,並做處理。
我們可能會基於不同的目的,實作出多個 CommandLineRunner。若想要求這些 runner 按照指定的順序去執行,則可使用 @Order
標記。
@Component
@Order(2)
public class MyRunnerA implements CommandLineRunner {
// ...
}
@Component
@Order(1)
public class MyRunnerB implements CommandLineRunner {
// ...
}
這個 @Order
標記可傳入一個數字參數,代表執行順序。Spring Boot 會讓數字小的先執行。
如果不使用此標記,將會依照類別名稱遞增的順序來執行,讀者可自行嘗試。
另外還有一個叫做「ApplicationRunner」的元件,可以達到與 CommandLineRunner 一樣的效果。程式的實作方式也雷同。
我們在第一節有提到,啟動 JAR 檔時可以附加一些參數選項。CommandLineRunner 與 ApplicationRunner 的差別,就是 run
方法包裝這些參數選項的方式不同罷了。
@Component
public class MyRunnerC implements ApplicationRunner {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("--- Non option args ---");
args.getNonOptionArgs().forEach(logger::info);
logger.info("--- Option values ---");
args.getOptionNames().forEach(name -> {
List<String> values = args.getOptionValues(name);
logger.info("{}: {}", name, String.join(",", values));
});
}
}
CommandLineRunner 是以 String
陣列包裝參數;而 ApplicationRunner 則是 ApplicationArguments
。前者比較 raw data,而後者能夠有系統地取出裡面的資料。
假設啟動 JAR 檔的指令如下。
java -jar XXX.jar --name=Vincent --msg=hello --msg=world ithome ironman
則以上 ApplicationRunner 印出的 log 內容如下。
這些選項參數並沒有規定只能輸入什麼,所以我們能附加任何自己想要的參數,傳遞給 ApplicationRunner 或 CommandLineRunner。
最後談談這些 runner 可以用在什麼時機。由於筆者見識尚淺,所以能提出的想法並不多。
在 Day 12 的文章,筆者有實作出寄 email 的功能。搭配 runner,可以在 server 成功啟動後,立即發送訊息給相關人員。或者有些公司會串接 Slack 等通訊軟體,也能在 server 啟動後通知大家。
筆者前公司的產品,有整合全文檢索引擎的搜尋功能,意即對資料的一至多個欄位做關鍵字查詢。為此,公司會把 DB 的資料傳送到 Elasticsearch 這款全文檢索引擎做保存。
而查詢 DB 時,若聯合太多表做 JOIN,會有查詢費時的問題。因此將某些業務所需要的 JOIN 好的資料,事先放在 Elasticsearch,或許是不錯的選擇。
在 server 啟動後,可以透過 runner 幫忙做這些事。
筆者前公司的系統,會將各個幣別的匯率放在 HashMap 中作為快取。然而 server 重新啟動後,這些資料理所當然會消失。
在 Day 16 的文章,筆者有實作出從第三方 API 取得匯率的程式。在 server 啟動後,可以透過 runner 開始取得匯率,並將資料放入快取,完成初始化的工作。
Ref:
Spring Boot 2 (七):Spring Boot 如何解決項目啟動時初始化資源
SpringBoot - 實現啟動時執行指定任務(CommandLineRunner、ApplicationRunner)
今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教