京東一面:如何在SpringBoot啟動時執行特定代碼?有哪些方式?

来源:https://www.cnblogs.com/coderacademy/p/18128072
-Advertisement-
Play Games

Spring Boot啟動時的初始化可通過監聽事件、實現介面及使用註解實現等方式,每種方式對應不同場景,確保應用啟動時關鍵邏輯執行。 ...


引言

Spring Boot 提供了許多便捷的功能和特性,使得開發者可以更加輕鬆地構建強大、高效的應用程式。然而,在應用程式啟動時執行一些初始化操作是至關重要的,它可以確保應用程式在啟動後處於預期的狀態,從而提供更好的用戶體驗和穩定性。

在應用程式啟動時執行初始化操作有許多好處。首先,它可以確保應用程式在啟動後的初始狀態是正確的,避免了在應用程式運行時出現意外情況。其次,它可以在應用程式準備好接受請求之前完成一些必要的設置,例如載入配置、建立資料庫連接、緩存預熱等。總的來說,執行初始化操作可以確保應用程式以正確的方式啟動,併為後續操作提供一個穩定的基礎。

image.png

監聽 ApplicationContext事件

Spring Boot應用程式啟動時執行初始化操作的方法是通過監聽ApplicationContext事件。ContextRefreshedEvent事件表示ApplicationContext被初始化或刷新時觸發的事件。通過監聽這個事件,開發者可以在應用程式啟動後執行一些必要的初始化操作。

image.png

示例:

@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("監聽到ContextRefreshedEvent事件,開始初始化操作。。。。。。。");
    }
}

這種方式適合以下場景:

  1. 執行一次性初始化操作: 當應用程式啟動時,可能需要執行一些只需在應用程式初始化階段執行一次的操作,例如載入基礎數據、建立連接等。通過監聽 ContextRefreshedEvent 事件,可以確保這些初始化操作在應用程式啟動後立即執行。

  2. 初始化緩存或緩存刷新: 如果應用程式使用了緩存,可能需要在應用程式啟動時初始化緩存或定期刷新緩存。通過監聽 ContextRefreshedEvent 事件,可以在應用程式啟動後立即執行緩存初始化或刷新操作,確保緩存數據是最新的。

  3. 執行與外部系統的交互: 在應用程式啟動時,可能需要與外部系統進行交互,例如檢查外部系統的可用性、載入配置信息等。通過監聽 ContextRefreshedEvent 事件,可以在應用程式啟動後立即執行與外部系統的交互操作,確保應用程式在啟動後處於正常工作狀態。

  4. 執行與 Spring Bean 相關的初始化操作: 在應用程式啟動時,可能需要執行一些與 Spring Bean 相關的初始化操作,例如在資料庫連接池初始化後執行資料庫遷移、在消息隊列連接初始化後執行訂閱操作等。通過監聽 ContextRefreshedEvent 事件,可以確保這些初始化操作在 Spring Bean 初始化完成後立即執行

這種方式能夠確保在 ApplicationContext 被完全初始化或刷新後執行初始化操作,可以在這個時機執行一些需要ApplicationContext完全準備好的操作。但是需要註意的是,ContextRefreshedEvent 事件可能會在應用程式的刷新周期內多次觸發,因此在處理這個事件時需要謹慎處理,避免重覆執行初始化邏輯。

實現CommandLineRunner介面

CommandLineRunner是Spring Boot提供的一個介面,它有一個run方法,當Spring Boot應用上下文初始化完成後,會自動查找並執行所有實現了CommandLineRunner介面的Bean的run方法。CommandLineRunner介面實際上是Spring Boot對Spring框架生命周期管理的一個擴展,通過對介面的實現,我們可以在Spring Boot應用啟動後的特定階段執行自定義的初始化邏輯。

image.png

示例:

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("MyCommandLineRunner.run()方法執行了");
    }
}

使用場景:

  1. 命令行參數處理CommandLineRunner介面常用於處理從命令行傳入的參數,例如運行不同模式下的任務(如dev模式、prod模式)、讀取配置項等。
  2. 應用啟動後的一次性操作:在應用啟動後,可能需要進行一些一次性執行的任務,如資料庫表結構檢查、初始化緩存、發送通知郵件等。

使用CommandLineRunner介面這種方式是,我們只需要實現介面,無需關註容器的生命周期事件或手動註冊監聽器。但是如果是多個CommandLineRunner之間的執行順序無法保證,可能會帶來不確定性(如果是不關心順序,那就不是缺點了)。另外,我們不應該在``
run方法中實現過多或較為複雜的任務。

實現ApplicationRunner介面

ApplicationRunner是Spring Boot提供的另一個介面,它也有一個run方法,與CommandLineRunner介面非常相似。當Spring Boot應用啟動並且ApplicationContext初始化完成後,Spring Boot會查找並執行所有實現了ApplicationRunner介面的Bean的run方法。

image.png

ApplicationRunner的主要特點是其run方法接收一個ApplicationArguments參數,它可以更好地解析和處理命令行參數,包括選項參數(鍵值對)和非選項參數。

示例:

@Component
public class ApplicationArgumentProcessor implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationArgumentProcessor.run()方法執行了");
    }
}

使用場景:

  1. 命令行參數解析:由於ApplicationArguments提供了豐富的參數解析能力,因此更適合處理帶有鍵值對形式的命令行參數,如--server-port=8080,然後根據這些參數執行不同的初始化操作。
@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()) {
            // 根據埠號進行特定的初始化操作
        }
    }
}
  1. 啟動時初始化:同CommandLineRunner,也可用於執行啟動後的一次性操作,例如讀取配置、初始化緩存、檢查系統資源等,同時可以根據解析的命令行參數決定初始化的具體內容。

相比較於CommandLineRunnerApplicationRunner提供了更強大的命令行參數解析功能,可以輕鬆處理各種類型的參數。可以根據命令行參數靈活調整啟動時的初始化邏輯。但是其缺點同CommandLineRunner

ApplicationRunnerCommandLineRunner都可以用來在Spring Boot啟動時執行特定代碼,兩者在應用場景上略有差異,具體選擇哪種取決於項目的實際需求和命令行參數的複雜程度。

使用@PostConstruct註解

@PostConstruct註解是JSR-250規範的一部分,Spring框架對此提供了支持。當Spring容器管理的Bean完成依賴註入後,會自動調用標註有@PostConstruct的方法。這個註解應用於無參或void返回值的方法上,表明該方法應在依賴註入完成後,但在Bean實例正式投入使用之前調用。

在Spring Boot啟動時,當Spring容器初始化並創建Bean時,如果發現某個Bean上有@PostConstruct註解的方法,則會在Bean的生命周期的初始化階段調用這個方法。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    
    @PostConstruct
    public void init() {
        // 在依賴註入完成後,執行初始化操作
        System.out.println("UserService初始化...");
        // 初始化資料庫連接、緩存或者其他內部狀態
    }
}

使用場景:

  1. 單個Bean初始化:對於某個特定的Bean,在其所有依賴項註入完成後,需要執行一些特定的初始化操作,例如資料庫連接初始化、緩存預熱、初始化內部狀態等。
  2. 資源初始化:對於一些公共資源,如線程池、資料庫連接池等,可以在對應的配置類或服務類中使用@PostConstruct來完成初始化設置。

@PostConstruct註解只需要在需要執行初始化操作的方法上加上即可,無需額外實現介面或關註Spring容器的生命周期事件。並且針對性強,僅針對單個Bean進行初始化操作,有助於提高代碼的模塊化和復用性。

但是如果有多個具有@PostConstruct註解的方法,它們之間沒有明確的執行順序,除非通過Bean間的依賴關係隱式確定順序。並且針對單個Bean進行初始化操作,所以他並不適合做全局性初始化操作。

@Bean註解中指定初始化方法

@Bean註解在Spring框架中用於定義一個Bean的實例化邏輯,通常在配置類中使用。通過在@Bean註解中指定initMethod屬性,可以設置一個在Bean實例化並完成依賴註入後執行的方法。當Spring容器創建並註入完所有依賴關係後,會自動調用該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("初始化構造函數-BeanWayService");
    }
}

適用場景:

  1. 資源初始化:例如,初始化資料庫連接、網路連接、線程池等資源。
  2. Bean狀態設置:在Bean實例化後,對其進行額外的狀態設定或配置。
  3. 緩存預熱:在服務啟動時預先載入部分數據至緩存中。

Bean實例上定義初始化方法,與Bean緊密關聯,可以精確地控制Bean在何時執行初始化操作,與Spring容器的生命周期綁定,尤其適用於那些需要在Bean實例化後立即執行的操作。。但是如果多個Bean都有初始化方法,它們之間的執行順序難以控制,除非依賴於Spring容器中Bean的依賴註入順序。

實現InitializingBean介面

InitializingBean是Spring框架中的一個介面,它包含一個方法afterPropertiesSet()。當Spring容器完成了對一個Bean的所有必要屬性的依賴註入後,如果該Bean實現了InitializingBean介面,Spring會自動調用其afterPropertiesSet()方法。

@Component
public class MyService implements InitializingBean {

    @Autowired
    private Dependency dependency;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 在所有依賴註入完成後執行的初始化邏輯
        System.out.println("MyService初始化...");
        // 初始化資源、設置狀態或執行其他操作
    }

    // 其他業務方法...
}

適用場景:

  1. 資源初始化:如初始化資料庫連接、網路連接、線程池等資源。
  2. Bean狀態設置:在依賴註入完成後,設置Bean的初始狀態或執行特定的配置操作。

afterPropertiesSet()方法會在所有屬性註入完成後執行,確保Bean在使用前完成初始化。不需要額外的註解,只需實現介面就可以定義初始化邏輯。但是其要求Bean實現特定介面,增加了類的耦合度,同時也不符合Spring倡導的基於註解的編程風格。並且需要顯式拋出異常。

相比較於@PostConstruct@PostConstruct註解更具語義化且不強制類實現介面,降低了耦合度。推薦優先考慮使用@PostConstruct註解進行初始化邏輯的編寫。

@EventListener註解

@EventListener 註解在Spring應用程式中定義事件監聽器。通過監聽 ApplicationReadyEvent事件,我們可以確保在應用程式完全啟動並準備好接受請求時執行初始化邏輯。通過在監聽器方法上添加 @EventListener 註解,並指定要監聽的事件類型,可以在事件發生時執行相應的初始化操作。

@Component
public class StartupEventListener {

    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReadyEvent(ApplicationReadyEvent event) {
        System.out.println("Spring Boot應用已啟動並準備就緒,開始執行初始化操作...");
        // 在這裡執行需要在應用啟動後進行的初始化代碼
    }
}

適用場景:

  1. 應用啟動後執行一次性操作:如數據初始化、緩存預熱、統計信息收集等。
  2. 等待所有Bean初始化後再執行:當需要確保所有Bean都已經初始化完畢再執行某些操作時。

通過事件驅動的方式,將初始化邏輯與Bean的創建邏輯解耦開來,並且可以監聽多種事件類型(例如:ContextRefreshedEvent),不僅僅是應用啟動事件,還可用於其他業務場景。相比於@PostConstructCommandLineRunnerApplicationRunner等機制,@EventListener監聽的ApplicationReadyEvent在Spring Boot啟動流程中的執行時機較晚,所有Bean都已經初始化並準備就緒後才會觸發。

總結

本文全面探討了Spring Boot啟動階段執行初始化操作的幾種常見方法,包括監聽事件、實現介面以及使用註解等多種策略,具體如下:

  1. 監聽ApplicationContext事件:通過實現ApplicationListener<ContextRefreshedEvent>介面,監聽ContextRefreshedEvent事件,可在Spring容器初始化完成後執行初始化邏輯。這種方式適用於需要在所有Bean載入完畢後進行全局性初始化操作的場景。

  2. 實現CommandLineRunner介面:Spring Boot啟動後,會自動調用實現了CommandLineRunner介面的Bean的run方法,該方法可以處理命令行參數並執行啟動時的特定操作。適用於需要根據命令行參數執行初始化邏輯或進行啟動後一次性任務的情況。

  3. 實現ApplicationRunner介面:與CommandLineRunner類似,ApplicationRunner也在Spring Boot啟動後執行其run方法,但其參數為ApplicationArguments,提供了更強大的命令行參數解析功能。適合處理鍵值對形式的命令行參數並據此執行初始化任務。

  4. 使用@PostConstruct註解:在Bean的方法上添加@PostConstruct註解,Spring會在該Bean的所有依賴註入完成後調用該方法進行初始化。這種方法用於單個Bean初始化完成後的特定邏輯,增強了代碼的模塊化和可維護性。

  5. @Bean註解中指定初始化方法:通過@Bean註解中的initMethod屬性指定Bean的初始化方法,該方法在Bean實例化並完成註入後由Spring容器調用。這種方法適用於需要對特定Bean進行精細化初始化管理的場景。

  6. 實現InitializingBean介面:Bean實現InitializingBean介面並重寫afterPropertiesSet方法,也能實現在依賴註入完成後執行初始化邏輯。雖然傳統但不如使用@PostConstruct註解優雅,且增加了類的耦合度。

  7. 使用@EventListener註解:通過監聽ApplicationReadyEvent等事件,可以在Spring Boot應用啟動並準備就緒後執行初始化任務。這種方式延遲執行,適用於在所有Bean初始化完畢且應用已經完全啟動後才需要進行的操作。

每種方法均有其適用場景和優缺點,我們應根據項目需求和具體情況選擇最適合的初始化方式。通過熟練掌握和靈活運用這些方法,能夠有效地管理和優化Spring Boot應用的啟動流程,確保應用程式在啟動之初即進入正常運作狀態。

本文已收錄於我的個人博客:碼農Academy的博客,專註分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構設計、面試題、程式員攻略等


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 演算法(Algorithm)是指用來操作數據、解決程式問題的一組方法。對於同一個問題,使用不同的演算法,也許最終得到的結果是一樣的,但在過程中消耗的資源和時間卻會有很大的區別 衡量不同演算法之間的優劣主要是通過時間和空間兩個維度去考量: 時間維度:是指執行當前演算法所消耗的時間,我們通常用「時間復 ...
  • vue3 快速入門系列 - vue3 路由 在vue3 基礎上加入路由。 vue3 需要使用 vue-router V4,相對於 v3,大部分的 Vue Router API 都沒有變化。 Tip:不瞭解路由的同學可以看一下筆者之前的文章:vue2 路由 參考:vue2 路由官網、vue3 路由官網 ...
  • 引言:隨著深度學習技術的發展進步,已經不再依賴強大的GPU算力,便可實現AI推理了,讓AI技術滲透到了電腦、手機、智能設備等各類設備。體育、健身行業也不例外,阿裡體育等IT大廠,推出的樂動力、天天跳繩、百分運動等AI運動APP,讓雲上運動會、線上運動會、健身打卡、AI體育指導、AI體測等概念空前火熱 ...
  • 前言 首先這篇文章只是初步的嘗試,不涉及過於高深的編程技巧;同時需要表明的是,面向對象只是一種思想,不局限於什麼樣的編程語言,不可否認的是基於面向對象特性而設計的語言確實要比面向過程式的語言更加容易進行抽象和統籌,可以說面向對象的設計模式可以很大程度上擺脫過程的實例,但要論完整的應用來講,設計模式也 ...
  • 淘寶詳情API介面是用於獲取淘寶商品詳細信息的介面,它允許開發者通過發送請求,獲取商品的描述、價格、評價等信息。下麵是一個關於淘寶詳情API介面的示例文檔,包括介面地址、請求參數、響應參數等內容。 淘寶詳情API介面文檔 一、介面地址 https://api-gw.onebound.cn/taoba ...
  • 大家好,我是R哥。 Nacos 2.3.2 前幾天正式發佈了,修複了一個重大 bug。 Nacos 先掃個盲: Nacos 一個用於構建雲原生應用的動態服務發現、配置管理和服務管理平臺,由阿裡巴巴開源,致力於發現、配置和管理微服務。 說白了,Nacos 就是充當微服務中的的註冊中心和配置中心。 推薦 ...
  • 拓展閱讀 MySQL View MySQL truncate table 與 delete 清空表的區別和坑 MySQL Ruler mysql 日常開發規範 MySQL datetime timestamp 以及如何自動更新,如何實現範圍查詢 MySQL 06 mysql 如何實現類似 oracl ...
  • Java的記憶體管理來說,就是ThreadLocal存在無法被GC回收的記憶體。這些無法被回收的記憶體,如果隨著時間的推移,從而導致超出記憶體容量「記憶體溢出」,最終導致程式崩潰「OutOfMemoryError」。所以為了避免我們的Java程式崩潰,我們必須要避免出現記憶體泄漏的問題。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...