使用Spring啟動時運行自定義業務
在Spring應用啟動時運行自定義業務的場景很常見,但應用不當也可能會導致一些問題。
基於Spring控制反轉(Inverse of Control)功能用戶幾乎不用幹預bean實例化過程,對於自定義業務則需要控制部分流程及容器,因此值得須特別關註。
1. Spring啟動時運行自定義業務
我們不能簡單包括自定義業務在bean的構造函數或在實例化任何對象之後調用方法,這些過程不由我們控制。請看示例:
@Component public class InvalidInitExampleBean { @Autowired private Environment env; public InvalidInitExampleBean() { env.getActiveProfiles(); } }
這裡嘗試在構造函數中訪問自動裝配的屬性。當調用構造函數時,Spring bean仍沒有全部初始化,因此導致NullPointerExceptions異常。下面介紹幾種方式解決此問題。
1.1 @PostConstruct 註解
@PostConstruct註解用於方法上,實現bean初始化後立刻執行一次。需要註意的是,即使沒有對象註入,Spring也會執行註解方法。
@Component public class PostConstructExampleBean { private static final Logger LOG = Logger.getLogger(PostConstructExampleBean.class); @Autowired private Environment environment; @PostConstruct public void init() { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
上面示例可以實現Environment environment被安全註入,然後調用註解方法且不會出現空指針異常。
1.2 InitializingBean 接口
InitializingBean接口實現功能與上節類似。但需要實現接口並重寫afterPropertiesSet方法。
下面重寫前節的示例:
@Component public class InitializingBeanExampleBean implements InitializingBean { private static final Logger LOG = Logger.getLogger(InitializingBeanExampleBean.class); @Autowired private Environment environment; @Override public void afterPropertiesSet() throws Exception { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
1.3 ApplicationListener 監聽器
該方法可用於在Spring上下文初始化之後執行自定義業務。因此不針對特定bean,而是等待所有bean初始化之後。應用時需要實現ApplicationListener接口:
@Component public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> { private static final Logger LOG = Logger.getLogger(StartupApplicationListenerExample.class); public static int counter; @Override public void onApplicationEvent(ContextRefreshedEvent event) { LOG.info("Increment counter"); counter++; } }
同樣可以引入@EventListener註解實現:
@Component public class EventListenerExampleBean { private static final Logger LOG = Logger.getLogger(EventListenerExampleBean.class); public static int counter; @EventListener public void onApplicationEvent(ContextRefreshedEvent event) { LOG.info("Increment counter"); counter++; } }
上面示例使用ContextRefreshedEvent,具體選擇哪種事件根據你的業務需要。
1.4 @Bean的初始化方法
該註解的initMethod屬性可用於在bean初始化之後執行方法,示例:
public class InitMethodExampleBean { private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class); @Autowired private Environment environment; public void init() { LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
既不要實現接口,也不要特定註解。通過註解定義Bean:
@Bean(initMethod="init") public InitMethodExampleBean initMethodExampleBean() { return new InitMethodExampleBean(); }
對應xml配置:
<bean id="initMethodExampleBean" class="com.baeldung.startup.InitMethodExampleBean" init-method="init"> </bean>
1.5 構造函數註入
如果使用構造器註入屬性,可以簡單地在構造函數中包括業務:
@Component public class LogicInConstructorExampleBean { private static final Logger LOG = Logger.getLogger(LogicInConstructorExampleBean.class); private final Environment environment; @Autowired public LogicInConstructorExampleBean(Environment environment) { this.environment = environment; LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
1.6 Spring Boot CommandLineRunner接口
Spring Boot 提供瞭CommandLineRunner接口,重寫run方法,可以在應用啟動時Spring應用上下文實例化之後調用。
@Component public class CommandLineAppStartupRunner implements CommandLineRunner { private static final Logger LOG = LoggerFactory.getLogger(CommandLineAppStartupRunner.class); public static int counter; @Override public void run(String...args) throws Exception { LOG.info("Increment counter"); counter++; } }
CommandLineRunner bean在相同上下文中可以定義多個,通過使用Ordered 接口或@Ordere註解確定順序。
1.7 Spring Boot ApplicationRunner
與CommandLineRunner類似,Spring Boot 也提供瞭ApplicationRunner接口,重寫run方法可以實現應用啟動時執行自定義業務。另外其回調方法沒有使用String參數,而是使用ApplicationArguments類的實例。
ApplicationArguments有方法可以獲取可選參數及普通參數的值,參數前有–的表示可選參數。
@Component public class AppStartupRunner implements ApplicationRunner { private static final Logger LOG = LoggerFactory.getLogger(AppStartupRunner.class); public static int counter; @Override public void run(ApplicationArguments args) throws Exception { LOG.info("Application started with option names : {}", args.getOptionNames()); LOG.info("Increment counter"); counter++; } }
2. 執行順序
多種方法對bean同時進行控制,對應執行順序如下:
- 構造函數
- @PostConstruct註解方法
- InitializingBean的afterPropertiesSet()
- @Bean或xml中標註的初始化方法
讀者可以自行測試進行驗證。
3. 總結
本文介紹多種方式實現在Spring啟動時實現自定義業務。通過對比不同方式實現加深對Spring的理解,掌握更多控制bean實例化過程的方式。以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Spring啟動時實現初始化有哪些方式?
- Spring Boot中單例類實現對象的註入方式
- 淺談SpringBoot中的Bean初始化方法 @PostConstruct
- 基於@PostConstruct註解的使用,解決向靜態變量註入值
- Springboot啟動執行特定代碼的方式匯總