SpringBoot初始化加載配置的八種方式總結
- @PostConstruct 注解
- InitializingBean 接口
- @Bean initMethod方法
- 構造器注入
- ApplicationListener
- CommandLineRunner
- ApplicationRunner
- SmartLifecycle
序號 | 初始化加載方式 | 執(zhí)行時機 |
1 | @PostConstruct 注解(在方法加注解) | Bean對象初始化完成后執(zhí)行( 該方法會在所有依賴字段注入后才執(zhí)行 ) |
2 | 構造器注入(構造方法加 @Autowired注解 ) | Bean對象初始化完成后執(zhí)行( 該方法會在所有依賴字段注入后才執(zhí)行 ) |
3 | InitializingBean 接口(繼承 InitializingBean接口,并實現(xiàn) afterPropertiesSet()這個方法) | Bean對象初始化完成后執(zhí)行( 該方法會在所有依賴字段注入后才執(zhí)行 ) 3和4是一樣的,實現(xiàn)方式不同 |
4 | @Bean initMethod方法 | Bean對象初始化完成后執(zhí)行( 該方法會在所有依賴字段注入后才執(zhí)行 ) 3和4是一樣的,實現(xiàn)方式不同 |
5 | SmartLifecycle 接口(繼承SmartLifecycle 接口),并實現(xiàn)start() 方法 | SmartLifecycle 的執(zhí)行時機是在 Spring 應用上下文刷新完成之后,即所有的 Bean 都已經(jīng)被實例化和初始化之后。 |
6 | ApplicationListener(繼承 ApplicationListener接口,并實現(xiàn)onApplicationEvent()方法 與方法上加 @EventListener的效果一樣 ) | 所有的Bean都初始化完成后才會執(zhí)行方法 |
7 | CommandLineRunner 繼承 CommandLineRunner,繼承 run() 方法 ) | 應用啟動后執(zhí)行 |
8 | ApplicationRunner( 繼承 CommandLineRunner,繼承 run() 方法 ) | 應用啟動后執(zhí)行 |
背景
在日常開發(fā)時,我們常常需要 在SpringBoot 應用啟動時執(zhí)行某一段邏輯,如下面的場景:
獲取一些當前環(huán)境的配置或變量
向緩存數(shù)據(jù)庫寫入一些初始數(shù)據(jù)
連接某些第三方系統(tǒng),確認對方可以工作
在實現(xiàn)這些功能時,我們可能會遇到一些"坑"。 為了利用SpringBoot框架的便利性,我們不得不將整個應用的執(zhí)行控制權交給容器,于是造成了大家對于細節(jié)是一無所知的。那么在實現(xiàn)初始化邏輯代碼時就需要小心了,比如,我們并不能簡單的將初始化邏輯在Bean類的構造方法中實現(xiàn),類似下面的代碼:
@Component public class InvalidInitExampleBean { @Autowired private Environment env; public InvalidInitExampleBean() { env.getActiveProfiles(); } }
注意:這里,我們在InvalidInitExampleBean的構造方法中試圖訪問一個自動注入的env字段,當真正執(zhí)行時,你一定會得到一個空指針異常(NullPointerException)。原因在于,當構造方法被調(diào)用時,Spring上下文中的Environment這個Bean很可能還沒有被實例化,同時也仍未注入到當前對象,所以并不能這樣進行調(diào)用。
下面,我們來看看在SpringBoot中實現(xiàn)"安全初始化"的一些方法:
一、@PostConstruct 注解
@PostConstruct 注解其實是來自于 javax的擴展包中(大多數(shù)人的印象中是來自于Spring框架),它的作用在于聲明一個Bean對象初始化完成后執(zhí)行的方法。
來看看它的原始定義:
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
也就是說,該方法會在所有依賴字段注入后才執(zhí)行,當然這一動作也是由Spring框架執(zhí)行的。
示例:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Arrays; /** * @Description TODO * @date 2024/12/5 11:19 * @Version 1.0 * @Author gezongyang */ @Component @Slf4j public class InvalidInitExampleBean { @Autowired private Environment environment; /** * 該方法會在所有依賴字段(environment)注入后才執(zhí)行,當然這一動作也是由Spring框架執(zhí)行的。 */ @PostConstruct public void init() { //environment 已經(jīng)注入 log.info("@PostConstruct execute:{}",Arrays.asList(environment.getDefaultProfiles())); } }
二、實現(xiàn) InitializingBean 接口
InitializingBean 是由Spring框架提供的接口,其與@PostConstruct注解的工作原理非常類似。
如果不使用注解的話,你需要讓Bean實例繼承 InitializingBean接口,并實現(xiàn)afterPropertiesSet()這個方法。
示例:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @Description 繼承 InitializingBean接口,并實現(xiàn)afterPropertiesSet() * afterPropertiesSet() 會在所有依賴的字段(environment)注入后才執(zhí)行 * @date 2024/12/5 11:37 * @Version 1.0 * @Author gezongyang */ @Component @Slf4j public class InitializingBeanExampleBean implements InitializingBean { @Autowired private Environment environment; /** * 這個方法會在environment注入后執(zhí)行 */ @Override public void afterPropertiesSet() { //environment 已經(jīng)注入 log.info("InitializingBean execute:{}", Arrays.asList(environment.getDefaultProfiles())); } }
三、@Bean initMethod方法
我們在聲明一個Bean的時候,可以同時指定一個initMethod屬性,該屬性會指向Bean的一個方法,表示在初始化后執(zhí)行。
示例:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @Description 指定一個initMethod屬性,該屬性會指向Bean的一個方法,表示在初始化后執(zhí)行。 * @date 2024/12/5 11:37 * @Version 1.0 * @Author gezongyang */ @Component @Slf4j public class InitializingBean { @Autowired private Environment environment; /** * 這個方法會在environment注入后執(zhí)行 */ public void init() { //environment 已經(jīng)注入 log.info("@Bean initMethod execute:{}", Arrays.asList(environment.getDefaultProfiles())); } /** * 這里將initMethod指向init方法,相應的我們也需要在Bean中實現(xiàn)這個方法: * @return */ @Bean(initMethod="init") public InitializingBean exBean() { return new InitializingBean(); } }
四、構造器注入
如果依賴的字段在Bean的構造方法中聲明,那么Spring框架會先實例這些字段對應的Bean,再調(diào)用當前的構造方法。
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @Description 如果依賴的字段在Bean的構造方法中聲明,那么Spring框架會先實例這些字段對應的Bean, * 再調(diào)用當前的構造方法。 * @date 2024/12/5 14:11 * @Version 1.0 * @Author gezongyang */ @Slf4j @Component public class LogicInConstructorExampleBean { private final Environment environment; /** * LogicInConstructorExampleBean(Environment environment) 調(diào)用在 * Environment 對象實例化之后 * @param environment */ @Autowired public LogicInConstructorExampleBean(Environment environment) { //environment實例已初始化 this.environment = environment; log.info("LogicInConstructor:{}", Arrays.asList(environment.getDefaultProfiles())); } }
五、實現(xiàn)ApplicationListener 接口
ApplicationListener 是由 spring-context組件提供的一個接口,主要是用來監(jiān)聽 “容器上下文的生命周期事件”。它的定義如下:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
這里的event可以是任何一個繼承于ApplicationEvent的事件對象。 對于初始化工作來說,我們可以通過監(jiān)聽ContextRefreshedEvent這個事件來捕捉上下文初始化的時機。
示例:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; /** * @Description 當所有的Bean都初始化完成后,執(zhí)行onApplicationEvent 方法 * @date 2024/12/5 14:19 * @Version 1.0 * @Author gezongyang */ @Slf4j @Component public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> { public static int counter; /** * 這里的event可以是任何一個繼承于ApplicationEvent的事件對象。 * 對于初始化工作來說,我們可以通過監(jiān)聽ContextRefreshedEvent這個事件來捕捉上下文初始化的時機。 * @param event */ @Override public void onApplicationEvent(ContextRefreshedEvent event) { log.info("ApplicationListener run!"); counter++; } }
在Spring上下文初始化完成后,這里定義的方法將會被執(zhí)行。 與前面的 InitializingBean 不同的是,通過 ApplicationListener 監(jiān)聽的方式是全局性的,也就是當 所有 的 Bean 都 初始化完成 后才會執(zhí)行方法。
Spring 4.2 之后引入了新的 @EventListener注解,可以實現(xiàn)同樣的效果:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; /** * @Description TODO * @date 2024/12/5 14:24 * @Version 1.0 * @Author gezongyang */ @Slf4j @Component public class EventListenerExampleBean { public static int counter; /** * 所有的Bean都初始化完成后,執(zhí)行onApplicationEvent方法。 * @param event */ @EventListener public void onApplicationEvent(ContextRefreshedEvent event) { log.info("@EventListener execute", event.toString()); counter++; } }
六、實現(xiàn) CommandLineRunner 接口
SpringBoot 提供了一個CommanLineRunner接口,用來實現(xiàn)在應用啟動后的邏輯控制,其定義如下:
public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; }
此外,對于多個CommandLineRunner的情況下可以使用@Order注解來控制它們的順序。
案例:
1 定義一個CommandDemo實現(xiàn)CommandLineRunner,并納入到spring容器
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; /** * @Description 應用啟動后的回調(diào)函數(shù),在應用啟動后執(zhí)行此方法 * @date 2024/12/5 14:32 * @Version 1.0 * @Author gezongyang */ @Component @Slf4j public class CommandDemo implements CommandLineRunner { /** * 此方法在應用啟動后執(zhí)行 * @param args */ @Override public void run(String... args) { log.info("CommandLineRunner run!"); } }
2 配置參數(shù),然后執(zhí)行啟動類
3 打印結果:
=====應用已經(jīng)啟動成功======[aaa,bbb]
七、實現(xiàn) ApplicationRunner 接口
package org.springframework.boot; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; /** * Interface used to indicate that a bean should <em>run</em> when it is contained within * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined * within the same application context and can be ordered using the {@link Ordered} * interface or {@link Order @Order} annotation. * * @author Phillip Webb * @since 1.3.0 * @see CommandLineRunner */ @FunctionalInterface public interface ApplicationRunner { /** * Callback used to run the bean. * @param args incoming application arguments * @throws Exception on error */ void run(ApplicationArguments args) throws Exception; }
與 CommandLineRunner接口類似, Spring boot 還提供另一個ApplicationRunner 接口來實現(xiàn)初始化邏輯。不同的地方在于 ApplicationRunner.run()方法接受的是封裝好的ApplicationArguments參數(shù)對象,而不是簡單的字符串參數(shù)。ApplicationArguments是對參數(shù)做了進一步的處理,可以解析key=value形式,我們可以通過name來獲取value(而CommandLineRunner只是獲取key=value整體)
package org.springframework.boot; import java.util.List; import java.util.Set; /** * Provides access to the arguments that were used to run a {@link SpringApplication}. * * @author Phillip Webb * @since 1.3.0 */ public interface ApplicationArguments { /** * Return the raw unprocessed arguments that were passed to the application. * @return the arguments */ String[] getSourceArgs(); /** * Return the names of all option arguments. For example, if the arguments were * "--foo=bar --debug" would return the values {@code ["foo", "debug"]}. * @return the option names or an empty set */ Set<String> getOptionNames(); /** * Return whether the set of option arguments parsed from the arguments contains an * option with the given name. * @param name the name to check * @return {@code true} if the arguments contain an option with the given name */ boolean containsOption(String name); /** * Return the collection of values associated with the arguments option having the * given name. * <ul> * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty * collection ({@code []})</li> * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a * collection having one element ({@code ["bar"]})</li> * <li>if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"), * return a collection having elements for each value ({@code ["bar", "baz"]})</li> * <li>if the option is not present, return {@code null}</li> * </ul> * @param name the name of the option * @return a list of option values for the given name */ List<String> getOptionValues(String name); /** * Return the collection of non-option arguments parsed. * @return the non-option arguments or an empty list */ List<String> getNonOptionArgs(); }
ApplicationArguments可以接收key=value這樣的參數(shù),getOptionNames()方法可以得到key的集合,getOptionValues(String name)方法可以得到value這樣的集合
示例:
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; /** * @Description TODO * @date 2022/6/23 9:55 * @Version 1.0 * @Author gezongyang */ @Component public class ApplicationRunnerDemo implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("====getOptionNames======"+args.getOptionNames()); System.out.println("====getOptionValues====="+args.getOptionValues("key")); } }
配置參數(shù)啟動
打印結果:
====getOptionNames======[key] ====getOptionValues=====[value]
八、實現(xiàn)SmartLifecycle 接口
package org.springframework.context; public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable var1); }
SmartLifecycle 是 Spring Framework 中的一個接口,它擴展了 Lifecycle 接口,并提供了更靈活的生命周期管理功能。使用 SmartLifecycle 可以更精細地控制應用組件的啟動和關閉過程。以下是 SmartLifecycle 的一些主要特性:
特性:
isAutoStartup(): 返回一個布爾值,指示該組件是否應該在容器啟動時自動啟動。
getPhase(): 返回一個整數(shù)值,表示組件的啟動順序(階段)。較低的數(shù)字表示較早的啟動順序。
start(): 啟動組件,當返回時,組件應該已經(jīng)啟動完畢。
stop(Runnable callback): 停止組件,并且可以在停止完成后執(zhí)行提供的回調(diào)函數(shù)。
isRunning(): 返回一個布爾值,指示組件當前是否處于運行狀態(tài)。
案例:
package com.cfcc.teis.load; import lombok.extern.slf4j.Slf4j; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; /** * @Description SmartLifecycle 可以更精細地控制應用組件的啟動和關閉過程 * @date 2024/12/5 15:10 * @Version 1.0 * @Author gezongyang */ @Slf4j @Component public class SmartLifecycleDemo implements SmartLifecycle { private volatile boolean running = false; /** * SmartLifecycle run */ @Override public void start() { log.info("SmartLifecycle run!"); this.running = true; } @Override public void stop() { } /** * 在這里放置停止邏輯 * @param callback */ @Override public void stop(Runnable callback) { this.running = false; callback.run(); // 確?;卣{(diào)被執(zhí)行 } @Override public boolean isRunning() { return this.running; } /** * 組件將在Spring上下文加載后自動啟動 * @return */ @Override public boolean isAutoStartup() { return true; } /** * 定義啟動順序,數(shù)字越小越先啟動 * @return */ @Override public int getPhase() { return 0; } }
注意事項
當你實現(xiàn) SmartLifecycle 時,確保 start() 方法不會阻塞,因為它會延遲整個應用程序的啟動。如果需要長時間運行的任務,考慮將其放入單獨的線程中。
stop(Runnable callback) 方法中的回調(diào)是重要的,必須調(diào)用它來通知框架組件已經(jīng)停止。如果你不打算使用某些方法,可以不重寫它們;默認實現(xiàn)將提供合理的行為。
通過使用 SmartLifecycle,你可以更好地控制應用程序中不同組件的生命周期行為,這對于那些對啟動和關閉有特殊需求的應用特別有用。
SmartLifecycle 的執(zhí)行時機:
是在 Spring 應用上下文刷新完成之后,即所有的 Bean 都已經(jīng)被實例化和初始化之后。具體來說,Spring 容器在調(diào)用 finishRefresh() 方法時會觸發(fā)所有實現(xiàn)了 SmartLifecycle 接口的 Bean 的啟動過程。這是通過容器中的 LifecycleProcessor 來管理的,默認使用的是 DefaultLifecycleProcessor。
SmartLifecycle 執(zhí)行的具體流程:
上下文刷新完成后:當 Spring 應用上下文完成刷新(finishRefresh),意味著所有的 Bean 已經(jīng)被加載并初始化完畢。
初始化生命周期處理器:Spring 會初始化 LifecycleProcessor,如果沒有顯式定義,則創(chuàng)建一個默認的 DefaultLifecycleProcessor。
調(diào)用 onRefresh():接著,Spring 會調(diào)用 LifecycleProcessor 的 onRefresh() 方法,這將導致調(diào)用 startBeans() 方法。
根據(jù) phase 分組并排序:startBeans() 方法會獲取所有實現(xiàn)了 Lifecycle 接口的 Bean,并根據(jù)它們的 phase 屬性進行分組和排序。phase 值越小的 Bean 將越早啟動。
檢查 isAutoStartup():對于每個 SmartLifecycle Bean,如果它的 isAutoStartup() 方法返回 true,那么它的 start() 方法就會被自動調(diào)用。
執(zhí)行 start() 方法:按照 phase 排序后的順序,依次調(diào)用每個 SmartLifecycle Bean 的 start() 方法來啟動組件。
設置運行狀態(tài):start() 方法應該確保組件處于運行狀態(tài),并且 isRunning() 方法應返回 true。
同樣的邏輯也適用于停止組件的過程,但是是以相反的順序執(zhí)行的,即 phase 值較大的 Bean 會先停止。
因此,如果你希望在 Spring 容器完全準備好之后執(zhí)行某些任務,比如開啟消息監(jiān)聽、啟動定時任務等,你可以實現(xiàn) SmartLifecycle 接口并在 start() 方法中放置這些邏輯。同時,通過調(diào)整 getPhase() 返回的值,可以控制多個 SmartLifecycle Bean 之間的啟動順序。
以上就是SpringBoot初始化加載配置的八種方式總結的詳細內(nèi)容,更多關于SpringBoot初始化加載配置的資料請關注腳本之家其它相關文章!
相關文章
Java 數(shù)據(jù)結構之時間復雜度與空間復雜度詳解
算法復雜度分為時間復雜度和空間復雜度。其作用: 時間復雜度是度量算法執(zhí)行的時間長短;而空間復雜度是度量算法所需存儲空間的大小2021-11-11Mybatis-Plus的saveOrUpdateBatch(null)問題及解決
這篇文章主要介紹了Mybatis-Plus的saveOrUpdateBatch(null)問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07MyBatis-Plus中如何使用ResultMap的方法示例
本文主要介紹了MyBatis-Plus中如何使用ResultMap,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11