京東一面:如何在SpringBoot啟動時執(zhí)行特定代碼?
引言
Spring Boot 提供了許多便捷的功能和特性,使得開發(fā)者可以更加輕松地構(gòu)建強大、高效的應(yīng)用程序。然而,在應(yīng)用程序啟動時執(zhí)行一些初始化操作是至關(guān)重要的,它可以確保應(yīng)用程序在啟動后處于預(yù)期的狀態(tài),從而提供更好的用戶體驗和穩(wěn)定性。
在應(yīng)用程序啟動時執(zhí)行初始化操作有許多好處。首先,它可以確保應(yīng)用程序在啟動后的初始狀態(tài)是正確的,避免了在應(yīng)用程序運行時出現(xiàn)意外情況。其次,它可以在應(yīng)用程序準備好接受請求之前完成一些必要的設(shè)置,例如加載配置、建立數(shù)據(jù)庫連接、緩存預(yù)熱等。總的來說,執(zhí)行初始化操作可以確保應(yīng)用程序以正確的方式啟動,并為后續(xù)操作提供一個穩(wěn)定的基礎(chǔ)。
圖片
監(jiān)聽 ApplicationContext事件
Spring Boot應(yīng)用程序啟動時執(zhí)行初始化操作的方法是通過監(jiān)聽ApplicationContext事件。ContextRefreshedEvent事件表示ApplicationContext被初始化或刷新時觸發(fā)的事件。通過監(jiān)聽這個事件,開發(fā)者可以在應(yīng)用程序啟動后執(zhí)行一些必要的初始化操作。
圖片
示例:
@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("監(jiān)聽到ContextRefreshedEvent事件,開始初始化操作。。。。。。。");
}
}
這種方式適合以下場景:
- 1. 執(zhí)行一次性初始化操作: 當應(yīng)用程序啟動時,可能需要執(zhí)行一些只需在應(yīng)用程序初始化階段執(zhí)行一次的操作,例如加載基礎(chǔ)數(shù)據(jù)、建立連接等。通過監(jiān)聽 ContextRefreshedEvent 事件,可以確保這些初始化操作在應(yīng)用程序啟動后立即執(zhí)行。
- 2. 初始化緩存或緩存刷新: 如果應(yīng)用程序使用了緩存,可能需要在應(yīng)用程序啟動時初始化緩存或定期刷新緩存。通過監(jiān)聽 ContextRefreshedEvent 事件,可以在應(yīng)用程序啟動后立即執(zhí)行緩存初始化或刷新操作,確保緩存數(shù)據(jù)是最新的。
- 3. 執(zhí)行與外部系統(tǒng)的交互: 在應(yīng)用程序啟動時,可能需要與外部系統(tǒng)進行交互,例如檢查外部系統(tǒng)的可用性、加載配置信息等。通過監(jiān)聽 ContextRefreshedEvent 事件,可以在應(yīng)用程序啟動后立即執(zhí)行與外部系統(tǒng)的交互操作,確保應(yīng)用程序在啟動后處于正常工作狀態(tài)。
- 4. 執(zhí)行與 Spring Bean 相關(guān)的初始化操作: 在應(yīng)用程序啟動時,可能需要執(zhí)行一些與 Spring Bean 相關(guān)的初始化操作,例如在數(shù)據(jù)庫連接池初始化后執(zhí)行數(shù)據(jù)庫遷移、在消息隊列連接初始化后執(zhí)行訂閱操作等。通過監(jiān)聽 ContextRefreshedEvent 事件,可以確保這些初始化操作在 Spring Bean 初始化完成后立即執(zhí)行
這種方式能夠確保在 ApplicationContext 被完全初始化或刷新后執(zhí)行初始化操作,可以在這個時機執(zhí)行一些需要ApplicationContext完全準備好的操作。但是需要注意的是,ContextRefreshedEvent 事件可能會在應(yīng)用程序的刷新周期內(nèi)多次觸發(fā),因此在處理這個事件時需要謹慎處理,避免重復(fù)執(zhí)行初始化邏輯。
實現(xiàn)CommandLineRunner接口
CommandLineRunner是Spring Boot提供的一個接口,它有一個run方法,當Spring Boot應(yīng)用上下文初始化完成后,會自動查找并執(zhí)行所有實現(xiàn)了CommandLineRunner接口的Bean的run方法。CommandLineRunner接口實際上是Spring Boot對Spring框架生命周期管理的一個擴展,通過對接口的實現(xiàn),我們可以在Spring Boot應(yīng)用啟動后的特定階段執(zhí)行自定義的初始化邏輯。
圖片
示例:
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("MyCommandLineRunner.run()方法執(zhí)行了");
}
}
使用場景:
- 1. 命令行參數(shù)處理:CommandLineRunner接口常用于處理從命令行傳入的參數(shù),例如運行不同模式下的任務(wù)(如dev模式、prod模式)、讀取配置項等。
- 2. 應(yīng)用啟動后的一次性操作:在應(yīng)用啟動后,可能需要進行一些一次性執(zhí)行的任務(wù),如數(shù)據(jù)庫表結(jié)構(gòu)檢查、初始化緩存、發(fā)送通知郵件等。
使用CommandLineRunner接口這種方式是,我們只需要實現(xiàn)接口,無需關(guān)注容器的生命周期事件或手動注冊監(jiān)聽器。但是如果是多個CommandLineRunner之間的執(zhí)行順序無法保證,可能會帶來不確定性(如果是不關(guān)心順序,那就不是缺點了)。另外,我們不應(yīng)該在`` run方法中實現(xiàn)過多或較為復(fù)雜的任務(wù)。
實現(xiàn)ApplicationRunner接口
ApplicationRunner是Spring Boot提供的另一個接口,它也有一個run方法,與CommandLineRunner接口非常相似。當Spring Boot應(yīng)用啟動并且ApplicationContext初始化完成后,Spring Boot會查找并執(zhí)行所有實現(xiàn)了ApplicationRunner接口的Bean的run方法。
圖片
ApplicationRunner的主要特點是其run方法接收一個ApplicationArguments參數(shù),它可以更好地解析和處理命令行參數(shù),包括選項參數(shù)(鍵值對)和非選項參數(shù)。
示例:
@Component
public class ApplicationArgumentProcessor implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationArgumentProcessor.run()方法執(zhí)行了");
}
}
使用場景:
- 命令行參數(shù)解析:由于ApplicationArguments提供了豐富的參數(shù)解析能力,因此更適合處理帶有鍵值對形式的命令行參數(shù),如--server-port=8080,然后根據(jù)這些參數(shù)執(zhí)行不同的初始化操作。
@Component
public class ApplicationArgumentProcessor implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
Optional<Integer> port = args.getOptionValues("server-port").stream()
.map(Integer::parseInt)
.findFirst();
if (port.isPresent()) {
// 根據(jù)端口號進行特定的初始化操作
}
}
}
- 啟動時初始化:同CommandLineRunner,也可用于執(zhí)行啟動后的一次性操作,例如讀取配置、初始化緩存、檢查系統(tǒng)資源等,同時可以根據(jù)解析的命令行參數(shù)決定初始化的具體內(nèi)容。
相比較于CommandLineRunner,ApplicationRunner提供了更強大的命令行參數(shù)解析功能,可以輕松處理各種類型的參數(shù)??梢愿鶕?jù)命令行參數(shù)靈活調(diào)整啟動時的初始化邏輯。但是其缺點同CommandLineRunner。
ApplicationRunner和CommandLineRunner都可以用來在Spring Boot啟動時執(zhí)行特定代碼,兩者在應(yīng)用場景上略有差異,具體選擇哪種取決于項目的實際需求和命令行參數(shù)的復(fù)雜程度。
使用@PostConstruct注解
@PostConstruct注解是JSR-250規(guī)范的一部分,Spring框架對此提供了支持。當Spring容器管理的Bean完成依賴注入后,會自動調(diào)用標注有@PostConstruct的方法。這個注解應(yīng)用于無參或void返回值的方法上,表明該方法應(yīng)在依賴注入完成后,但在Bean實例正式投入使用之前調(diào)用。
在Spring Boot啟動時,當Spring容器初始化并創(chuàng)建Bean時,如果發(fā)現(xiàn)某個Bean上有@PostConstruct注解的方法,則會在Bean的生命周期的初始化階段調(diào)用這個方法。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@PostConstruct
public void init() {
// 在依賴注入完成后,執(zhí)行初始化操作
System.out.println("UserService初始化...");
// 初始化數(shù)據(jù)庫連接、緩存或者其他內(nèi)部狀態(tài)
}
}
使用場景:
1. 單個Bean初始化:對于某個特定的Bean,在其所有依賴項注入完成后,需要執(zhí)行一些特定的初始化操作,例如數(shù)據(jù)庫連接初始化、緩存預(yù)熱、初始化內(nèi)部狀態(tài)等。
2. 資源初始化:對于一些公共資源,如線程池、數(shù)據(jù)庫連接池等,可以在對應(yīng)的配置類或服務(wù)類中使用@PostConstruct來完成初始化設(shè)置。
@PostConstruct注解只需要在需要執(zhí)行初始化操作的方法上加上即可,無需額外實現(xiàn)接口或關(guān)注Spring容器的生命周期事件。并且針對性強,僅針對單個Bean進行初始化操作,有助于提高代碼的模塊化和復(fù)用性。
但是如果有多個具有@PostConstruct注解的方法,它們之間沒有明確的執(zhí)行順序,除非通過Bean間的依賴關(guān)系隱式確定順序。并且針對單個Bean進行初始化操作,所以他并不適合做全局性初始化操作。
@Bean注解中指定初始化方法
@Bean注解在Spring框架中用于定義一個Bean的實例化邏輯,通常在配置類中使用。通過在@Bean注解中指定initMethod屬性,可以設(shè)置一個在Bean實例化并完成依賴注入后執(zhí)行的方法。當Spring容器創(chuàng)建并注入完所有依賴關(guān)系后,會自動調(diào)用該Bean上指定的初始化方法。
@Configuration
public class PrePostConfig {
/**
* 指定初始化init
* @return
*/
@Bean(initMethod = "init")
BeanWayService beanWayService(){
return new BeanWayService();
}
}
public class BeanWayService {
public void init() {
System.out.println("@Bean-init-method");
}
public BeanWayService(){
super();
System.out.println("初始化構(gòu)造函數(shù)-BeanWayService");
}
}
適用場景:
1. 資源初始化:例如,初始化數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接、線程池等資源。
2. Bean狀態(tài)設(shè)置:在Bean實例化后,對其進行額外的狀態(tài)設(shè)定或配置。
3. 緩存預(yù)熱:在服務(wù)啟動時預(yù)先加載部分數(shù)據(jù)至緩存中。
Bean實例上定義初始化方法,與Bean緊密關(guān)聯(lián),可以精確地控制Bean在何時執(zhí)行初始化操作,與Spring容器的生命周期綁定,尤其適用于那些需要在Bean實例化后立即執(zhí)行的操作。。但是如果多個Bean都有初始化方法,它們之間的執(zhí)行順序難以控制,除非依賴于Spring容器中Bean的依賴注入順序。
實現(xiàn)InitializingBean接口
InitializingBean是Spring框架中的一個接口,它包含一個方法afterPropertiesSet()。當Spring容器完成了對一個Bean的所有必要屬性的依賴注入后,如果該Bean實現(xiàn)了InitializingBean接口,Spring會自動調(diào)用其afterPropertiesSet()方法。
@Component
public class MyService implements InitializingBean {
@Autowired
private Dependency dependency;
@Override
public void afterPropertiesSet() throws Exception {
// 在所有依賴注入完成后執(zhí)行的初始化邏輯
System.out.println("MyService初始化...");
// 初始化資源、設(shè)置狀態(tài)或執(zhí)行其他操作
}
// 其他業(yè)務(wù)方法...
}
適用場景:
1. 資源初始化:如初始化數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接、線程池等資源。
2. Bean狀態(tài)設(shè)置:在依賴注入完成后,設(shè)置Bean的初始狀態(tài)或執(zhí)行特定的配置操作。
afterPropertiesSet()方法會在所有屬性注入完成后執(zhí)行,確保Bean在使用前完成初始化。不需要額外的注解,只需實現(xiàn)接口就可以定義初始化邏輯。但是其要求Bean實現(xiàn)特定接口,增加了類的耦合度,同時也不符合Spring倡導(dǎo)的基于注解的編程風格。并且需要顯式拋出異常。
相比較于@PostConstruct,@PostConstruct注解更具語義化且不強制類實現(xiàn)接口,降低了耦合度。推薦優(yōu)先考慮使用@PostConstruct注解進行初始化邏輯的編寫。
@EventListener注解
@EventListener 注解在Spring應(yīng)用程序中定義事件監(jiān)聽器。通過監(jiān)聽 ApplicationReadyEvent事件,我們可以確保在應(yīng)用程序完全啟動并準備好接受請求時執(zhí)行初始化邏輯。通過在監(jiān)聽器方法上添加 @EventListener 注解,并指定要監(jiān)聽的事件類型,可以在事件發(fā)生時執(zhí)行相應(yīng)的初始化操作。
@Component
public class StartupEventListener {
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
System.out.println("Spring Boot應(yīng)用已啟動并準備就緒,開始執(zhí)行初始化操作...");
// 在這里執(zhí)行需要在應(yīng)用啟動后進行的初始化代碼
}
}
適用場景:
1. 應(yīng)用啟動后執(zhí)行一次性操作:如數(shù)據(jù)初始化、緩存預(yù)熱、統(tǒng)計信息收集等。
2. 等待所有Bean初始化后再執(zhí)行:當需要確保所有Bean都已經(jīng)初始化完畢再執(zhí)行某些操作時。
通過事件驅(qū)動的方式,將初始化邏輯與Bean的創(chuàng)建邏輯解耦開來,并且可以監(jiān)聽多種事件類型(例如:ContextRefreshedEvent),不僅僅是應(yīng)用啟動事件,還可用于其他業(yè)務(wù)場景。相比于@PostConstruct、CommandLineRunner或ApplicationRunner等機制,@EventListener監(jiān)聽的ApplicationReadyEvent在Spring Boot啟動流程中的執(zhí)行時機較晚,所有Bean都已經(jīng)初始化并準備就緒后才會觸發(fā)。
總結(jié)
本文全面探討了Spring Boot啟動階段執(zhí)行初始化操作的幾種常見方法,包括監(jiān)聽事件、實現(xiàn)接口以及使用注解等多種策略,具體如下:
1. 監(jiān)聽ApplicationContext事件:通過實現(xiàn)ApplicationListener<ContextRefreshedEvent>接口,監(jiān)聽ContextRefreshedEvent事件,可在Spring容器初始化完成后執(zhí)行初始化邏輯。這種方式適用于需要在所有Bean加載完畢后進行全局性初始化操作的場景。
2. 實現(xiàn)CommandLineRunner接口:Spring Boot啟動后,會自動調(diào)用實現(xiàn)了CommandLineRunner接口的Bean的run方法,該方法可以處理命令行參數(shù)并執(zhí)行啟動時的特定操作。適用于需要根據(jù)命令行參數(shù)執(zhí)行初始化邏輯或進行啟動后一次性任務(wù)的情況。
3. 實現(xiàn)ApplicationRunner接口:與CommandLineRunner類似,ApplicationRunner也在Spring Boot啟動后執(zhí)行其run方法,但其參數(shù)為ApplicationArguments,提供了更強大的命令行參數(shù)解析功能。適合處理鍵值對形式的命令行參數(shù)并據(jù)此執(zhí)行初始化任務(wù)。
4. 使用@PostConstruct注解:在Bean的方法上添加@PostConstruct注解,Spring會在該Bean的所有依賴注入完成后調(diào)用該方法進行初始化。這種方法用于單個Bean初始化完成后的特定邏輯,增強了代碼的模塊化和可維護性。
5. @Bean注解中指定初始化方法:通過@Bean注解中的initMethod屬性指定Bean的初始化方法,該方法在Bean實例化并完成注入后由Spring容器調(diào)用。這種方法適用于需要對特定Bean進行精細化初始化管理的場景。
6. 實現(xiàn)InitializingBean接口:Bean實現(xiàn)InitializingBean接口并重寫afterPropertiesSet方法,也能實現(xiàn)在依賴注入完成后執(zhí)行初始化邏輯。雖然傳統(tǒng)但不如使用@PostConstruct注解優(yōu)雅,且增加了類的耦合度。
7. 使用@EventListener注解:通過監(jiān)聽ApplicationReadyEvent等事件,可以在Spring Boot應(yīng)用啟動并準備就緒后執(zhí)行初始化任務(wù)。這種方式延遲執(zhí)行,適用于在所有Bean初始化完畢且應(yīng)用已經(jīng)完全啟動后才需要進行的操作。
每種方法均有其適用場景和優(yōu)缺點,我們應(yīng)根據(jù)項目需求和具體情況選擇最適合的初始化方式。通過熟練掌握和靈活運用這些方法,能夠有效地管理和優(yōu)化Spring Boot應(yīng)用的啟動流程,確保應(yīng)用程序在啟動之初即進入正常運作狀態(tài)。