一文詳解SpringBoot中CommandLineRunner接口
前言
Spring Boot的CommandLineRunner接口是一個(gè)函數(shù)式接口,用于在Spring Boot應(yīng)用程序啟動(dòng)后執(zhí)行一些初始化操作。它提供了一個(gè)run方法,該方法在應(yīng)用程序啟動(dòng)后被調(diào)用。
使用CommandLineRunner接口,可以在應(yīng)用程序啟動(dòng)后執(zhí)行一些必要的初始化操作,例如加載配置文件、初始化數(shù)據(jù)庫(kù)連接、創(chuàng)建默認(rèn)數(shù)據(jù)等??梢酝ㄟ^(guò)實(shí)現(xiàn)CommandLineRunner接口,并重寫(xiě)run方法來(lái)定義自己的初始化邏輯。
實(shí)例
導(dǎo)入庫(kù)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> </parent> <groupId>org.example</groupId> <artifactId>springboot-CommandLineRunner</artifactId> <version>1.0-SNAPSHOT</version> <name>Spring Boot banner</name> <description>Spring Boot and commandLineRunner</description> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
application.yaml
server: port: 8080 spring: profiles: active: dev
Runner
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component @Slf4j public class Runner implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("The Runner start to initialize ..."); } }
SpringBootCommandLineRunnerApplication
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @Slf4j public class SpringBootCommandLineRunnerApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCommandLineRunnerApplication.class, args); log.info("The service to end"); } }
執(zhí)行結(jié)果
在上面的示例中,我們創(chuàng)建了一個(gè)名為MyCommandLineRunner的類(lèi),并實(shí)現(xiàn)了CommandLineRunner接口。在run方法中,我們可以編寫(xiě)需要在應(yīng)用程序啟動(dòng)后執(zhí)行的初始化邏輯。
需要注意的是,實(shí)現(xiàn)CommandLineRunner接口的類(lèi)需要被Spring容器掃描到,可以使用@Component注解或其他方式將其注冊(cè)為Spring Bean。
先后順序示例
可以通過(guò)@Order()來(lái)設(shè)置Runner的先后順序,在上面例子的基礎(chǔ)上增加
OrderRunner1
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Order(1) @Slf4j public class OrderRunner1 implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("The OrderRunner1 start to initialize ..."); } }
OrderRunner2
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @Component @Order(2) @Slf4j public class OrderRunner2 implements CommandLineRunner { @Override public void run(String... args) throws Exception { log.info("The OrderRunner2 start to initialize ..."); } }
執(zhí)行結(jié)果
通常用法
加載初始化數(shù)據(jù)
可以實(shí)現(xiàn)CommandLineRunner接口,在run方法中加載一些初始化數(shù)據(jù)到數(shù)據(jù)庫(kù)等。適合做一些數(shù)據(jù)預(yù)加載工作。
示例
@Component public class DataInitializer implements CommandLineRunner { @Autowired private UserRepository userRepository; @Override public void run(String... args) throws Exception { // 創(chuàng)建初始用戶(hù) User admin = new User("admin", "123456"); userRepository.save(admin); User normalUser = new User("user", "123456"); userRepository.save(normalUser); System.out.println("數(shù)據(jù)加載完畢!"); } }
這里創(chuàng)建了一個(gè) DataInitializer 類(lèi),實(shí)現(xiàn) CommandLineRunner 接口。在 run() 方法中,我們注入了 UserRepository,然后創(chuàng)建了兩個(gè)用戶(hù)對(duì)象保存到數(shù)據(jù)庫(kù)中。這個(gè)類(lèi)會(huì)在 Spring Boot 應(yīng)用啟動(dòng)完成后執(zhí)行,從而實(shí)現(xiàn)了數(shù)據(jù)預(yù)加載的效果。通過(guò) CommandLineRunner,我們可以靈活地在 Spring Boot 啟動(dòng)時(shí)進(jìn)行一些初始化操作,如預(yù)先加載測(cè)試數(shù)據(jù)、插入管理員賬戶(hù)等,很好地增強(qiáng)了應(yīng)用的功能。
假設(shè)我們有一個(gè)User模型和用戶(hù)Repository,需要在Spring Boot啟動(dòng)時(shí)預(yù)加載幾個(gè)用戶(hù)數(shù)據(jù),可以這樣使用CommandLineRunner:
@Component public class DataInitializer implements CommandLineRunner { @Autowired private UserRepository userRepository; @Override public void run(String... args) throws Exception { // 清除所有數(shù)據(jù) userRepository.deleteAll(); // 創(chuàng)建幾個(gè)用戶(hù) User user1 = new User("John", "john@example.com"); User user2 = new User("Mary", "mary@example.com"); userRepository.save(user1); userRepository.save(user2); // 打印已保存用戶(hù)數(shù) System.out.println("Number of users saved: " + userRepository.count()); } }
這里我們實(shí)現(xiàn)了CommandLineRunner接口,然后注入U(xiǎn)serRepository bean。在run方法中,首先清空所有數(shù)據(jù),然后創(chuàng)建兩個(gè)用戶(hù)對(duì)象并保存,最后打印已保存的用戶(hù)數(shù)。這樣在Spring Boot應(yīng)用啟動(dòng)完成后,就會(huì)自動(dòng)執(zhí)行run方法,預(yù)加載指定的用戶(hù)數(shù)據(jù)。
啟動(dòng)后打印應(yīng)用信息
可以打印出一些應(yīng)用啟動(dòng)信息,如啟動(dòng)端口、運(yùn)行環(huán)境信息等,用于確認(rèn)應(yīng)用配置。
示例
@Component @Slf4j public class AppInfoPrinter implements CommandLineRunner { @Autowired private Environment environment; @Override public void run(String... args) throws Exception { log.info("========= 打印啟動(dòng)信息 ========="); // 打印應(yīng)用端口 log.info(("端口號(hào): " + environment.getProperty("server.port"))); // 打印當(dāng)前環(huán)境 log.info("當(dāng)前環(huán)境: " + environment.getProperty("spring.profiles.active")); // 打印JDK版本 log.info("JDK 版本: " + System.getProperty("java.version")); log.info("========= 打印啟動(dòng)信息結(jié)束 ========="); } }
執(zhí)行打印結(jié)果
啟動(dòng)異步任務(wù)
可以使用多線程啟動(dòng)一些異步任務(wù),進(jìn)行后臺(tái)數(shù)據(jù)處理等復(fù)雜業(yè)務(wù)邏輯。
示例
@Component @Slf4j public class AsyncTaskRunner implements CommandLineRunner { @Autowired private AsyncTaskService asyncTaskService; @Override public void run(String... args) throws Exception { log.info("========= 執(zhí)行任務(wù) ========="); // 在新線程中執(zhí)行任務(wù) new Thread(() -> { asyncTaskService.doTaskOne(); asyncTaskService.doTaskTwo(); asyncTaskService.doTaskThree(); }).start(); } } @Service @Slf4j class AsyncTaskService { public void doTaskOne() { log.info("執(zhí)行任務(wù)1"); } public void doTaskTwo() { log.info("執(zhí)行任務(wù)2"); } public void doTaskThree() { log.info("執(zhí)行任務(wù)3"); } }
執(zhí)行結(jié)果
[ main] org.example.runner.AsyncTaskRunner : ========= 執(zhí)行任務(wù) ========= [ Thread-1] org.example.runner.AsyncTaskService : 執(zhí)行任務(wù)1 [ Thread-1] org.example.runner.AsyncTaskService : 執(zhí)行任務(wù)2 [ Thread-1] org.example.runner.AsyncTaskService : 執(zhí)行任務(wù)3
接口健康檢查
可以調(diào)用并驗(yàn)證依賴(lài)服務(wù)的健康狀態(tài),如果不正??梢越K止Spring Boot啟動(dòng)。
示例
@Component @Slf4j public class HealthCheckRunner implements CommandLineRunner { @Autowired private DatabaseService databaseService; @Autowired private MessageQueueService messageQueueService; @Override public void run(String... args) throws Exception { if(!databaseService.isConnected()) { log.error("數(shù)據(jù)庫(kù)服務(wù)不可用,退出應(yīng)用!"); System.exit(1); } if(!messageQueueService.isConnected()) { log.error("消息隊(duì)列服務(wù)不可用,退出應(yīng)用!"); System.exit(1); } log.info("所有服務(wù)正常,應(yīng)用啟動(dòng)。"); } }
這里我們注入兩個(gè)依賴(lài)服務(wù) DatabaseService 和 MessageQueueService。在run方法中,調(diào)用它們的健康檢查方法,如果任何一個(gè)服務(wù)不可用,則直接調(diào)用System.exit(1)退出Spring Boot應(yīng)用啟動(dòng)。
外部服務(wù)調(diào)用
可以在啟動(dòng)時(shí)調(diào)用外部服務(wù),進(jìn)行驗(yàn)證、數(shù)據(jù)同步等操作。
示例
@Component public class OtherServiceCheckRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { // 健康檢查的URL String healthCheckUrl = "http://localhost:8080/actuator/health"; RestTemplate restTemplate = new RestTemplate(); // 發(fā)送GET請(qǐng)求進(jìn)行健康檢查 String response = restTemplate.getForObject(healthCheckUrl, String.class); // 根據(jù)響應(yīng)判斷健康狀態(tài) if (response.contains("\"status\":\"UP\"")) { System.out.println("Application is healthy"); } else { System.out.println("Application is not healthy"); } } }
參數(shù)校驗(yàn)
可以對(duì)輸入的運(yùn)行參數(shù)做校驗(yàn),如果不滿足條件可以終止Spring Boot啟動(dòng)。
示例
@Component @Slf4j public class ParameterValidator implements CommandLineRunner { @Override public void run(String... args) throws Exception { // 校驗(yàn)參數(shù)1 if(args.length < 2) { log.error("參數(shù)不正確,請(qǐng)傳入至少2個(gè)參數(shù)!"); System.exit(1); } // 校驗(yàn)參數(shù)2是否為數(shù)字 if(!args[1].matches("\\d+")) { log.error("第二個(gè)參數(shù)必須是數(shù)字!"); System.exit(1); } // 校驗(yàn)通過(guò),應(yīng)用繼續(xù)啟動(dòng) log.info("參數(shù)校驗(yàn)通過(guò),應(yīng)用啟動(dòng)中..."); } }
在run方法中,我們可以對(duì)main方法輸入的參數(shù)args進(jìn)行自定義校驗(yàn):
檢查參數(shù)數(shù)量校驗(yàn)參數(shù)類(lèi)型
如果參數(shù)不滿足需求,可以直接調(diào)用System.exit(1)來(lái)終止Spring Boot的啟動(dòng)。這樣就可以在應(yīng)用啟動(dòng)前驗(yàn)證參數(shù)的正確性,避免應(yīng)用啟動(dòng)后發(fā)生未知錯(cuò)誤。
動(dòng)態(tài)設(shè)置配置
可以根據(jù)運(yùn)行參數(shù)等條件動(dòng)態(tài)設(shè)置Spring Boot的配置,實(shí)現(xiàn)不同環(huán)境的適配。
示例 application.yaml
myconfig: foo: 十五 bar: 1
MyConfig
@Component @Data @ConfigurationProperties(prefix = "myconfig") public class MyConfig { private String foo; private int bar; // getter和setter方法省略 @Override public String toString() { return "MyConfig{" + "foo='" + foo + '\'' + ", bar=" + bar + '}'; } }
ConfigRunner
@Component @EnableConfigurationProperties(MyConfig.class) public class ConfigRunner implements CommandLineRunner { @Autowired private MyConfig myConfig; @Override public void run(String... args) throws Exception { // 打印當(dāng)前配置 System.out.println("Current config: " + myConfig); // 動(dòng)態(tài)設(shè)置配置 myConfig.setFoo("new value"); myConfig.setBar(100); // 打印更新后的配置 System.out.println("Updated config: " + myConfig); } }
啟動(dòng)阻塞
可以使應(yīng)用啟動(dòng)后阻塞住主線程,防止main方法直接退出,從而保持Spring Boot應(yīng)用運(yùn)行。
示例
@Component @Slf4j public class StartBlocker implements CommandLineRunner { @Override public void run(String... args) throws Exception { // 加載提示信息 log.info("正在等待管理員授權(quán)..."); // 等待授權(quán),阻塞啟動(dòng)流程 waitAuth(); // 授權(quán)完成后繼續(xù)啟動(dòng) log.info("管理員已授權(quán),應(yīng)用啟動(dòng)中..."); } private void waitAuth() { // 死循環(huán)模擬等待管理員操作授權(quán) while(true) { try { Thread.sleep(1000); } catch (InterruptedException e) { break; } } } }
總結(jié)
通過(guò) CommandLineRunner,我們可以深度控制 Spring Boot 應(yīng)用的啟動(dòng)流程,在應(yīng)用啟動(dòng)階段增強(qiáng)各種自定義邏輯。是 Spring Boot 提供的一個(gè)很實(shí)用的擴(kuò)展點(diǎn)。
以上就是一文詳解SpringBoot中CommandLineRunner接口的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot CommandLineRunner接口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot多數(shù)據(jù)源讀寫(xiě)分離的自定義配置問(wèn)題及解決方法
這篇文章主要介紹了SpringBoot多數(shù)據(jù)源讀寫(xiě)分離的自定義配置,我們可以通過(guò)自定義配置數(shù)據(jù)庫(kù)配置類(lèi)來(lái)解決這個(gè)問(wèn)題,方式有很多,不同的業(yè)務(wù)采用的方式也不同,下面我簡(jiǎn)單的介紹我們項(xiàng)目的使用的方法2022-06-06解決FeignClient發(fā)送post請(qǐng)求異常的問(wèn)題
這篇文章主要介紹了FeignClient發(fā)送post請(qǐng)求異常的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot Actuator未授權(quán)訪問(wèn)漏洞解決方案
工作的時(shí)候遇到過(guò)提示Spring Boot后端存在Actuator未授權(quán)訪問(wèn)漏洞,網(wǎng)上有很多詳細(xì)的解釋文章,在這里做一個(gè)簡(jiǎn)單的總結(jié)、介紹和分享,需要的朋友可以參考下2023-09-09java根據(jù)網(wǎng)絡(luò)地址保存圖片的方法
這篇文章主要為大家詳細(xì)介紹了java根據(jù)網(wǎng)絡(luò)地址保存圖片的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別(動(dòng)力節(jié)點(diǎn)java學(xué)院整理)
很多朋友不清楚面向?qū)ο蠛兔嫦蜻^(guò)程有什么區(qū)別,接下來(lái)小編給大家整理了關(guān)于面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別講解,感興趣的朋友可以參考下2017-04-04用SpringBoot+Vue+uniapp小程序?qū)崿F(xiàn)在線房屋裝修管理系統(tǒng)
這篇文章主要介紹了用SpringBoot+Vue+uniapp實(shí)現(xiàn)在線房屋裝修管理系統(tǒng),針對(duì)裝修樣板信息管理混亂,出錯(cuò)率高,信息安全性差,勞動(dòng)強(qiáng)度大,費(fèi)時(shí)費(fèi)力等問(wèn)題開(kāi)發(fā)了這套系統(tǒng),需要的朋友可以參考下2023-03-03springboot?項(xiàng)目啟動(dòng)后無(wú)日志輸出直接結(jié)束的解決
這篇文章主要介紹了springboot?項(xiàng)目啟動(dòng)后無(wú)日志輸出直接結(jié)束的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-1270行Java代碼實(shí)現(xiàn)深度神經(jīng)網(wǎng)絡(luò)算法分享
這篇文章主要介紹了70行Java代碼實(shí)現(xiàn)深度神經(jīng)網(wǎng)絡(luò)算法分享,涉及神經(jīng)網(wǎng)絡(luò)的計(jì)算過(guò)程,神經(jīng)網(wǎng)絡(luò)的算法程序?qū)崿F(xiàn),多層神經(jīng)網(wǎng)絡(luò)完整程序?qū)崿F(xiàn)等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以參考下。2017-11-11關(guān)于springboot中對(duì)sqlSessionFactoryBean的自定義
這篇文章主要介紹了springboot中對(duì)sqlSessionFactoryBean的自定義方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12