本文圍繞 Spring Boot 中如何讓你的 bean 在其他 bean 之前完成載入展開討論。 問題 今天有個小伙伴給我出了一個難題:在 SpringBoot 中如何讓自己的某個指定的 Bean 在其他 Bean 前完成被 Spring 載入?我聽到這個問題的第一反應是,為什麼會有這樣奇怪的需求 ...
本文圍繞 Spring Boot 中如何讓你的 bean 在其他 bean 之前完成載入展開討論。
問題
/**
* 系統屬性服務
**/
@Service
public class SystemConfigService {
// 訪問 db 的 mapper
private final SystemConfigMapper systemConfigMapper;
// 存放一些系統配置的緩存 map
private static Map<String, String>> SYS_CONF_CACHE = new HashMap<>()
// 使用構造方法完成依賴註入
public SystemConfigServiceImpl(SystemConfigMapper systemConfigMapper) {
this.systemConfigMapper = systemConfigMapper;
}
// Bean 的初始化方法,撈取資料庫中的數據,放入緩存的 map 中
@PostConstruct
public void init() {
// systemConfigMapper 訪問 DB,撈取數據放入緩存的 map 中
// SYS_CONF_CACHE.put(key, value);
// ...
}
// 對外提供獲得系統配置的 static 工具方法
public static String getSystemConfig(String key) {
return SYS_CONF_CACHE.get(key);
}
// 省略了從 DB 更新緩存的代碼
// ...
}
看過了上面的代碼後,很容易就理解了為什麼會標題中的需求了。
SpringBoot 官方文檔推薦做法
Constructor-based or setter-based DI? Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.
嘗試解決問題的一些方法
@Order 註解或者實現 org.springframework.core.Ordered
@AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore 註解
@DependsOn 註解
@Service
@DependsOn({"systemConfigService"})
public class BizService {
public BizService() {
String xxValue = SystemConfigService.getSystemConfig("xxKey");
// 可行
}
}
這樣測試下來是可以是可以的,就是操作起來也太麻煩了,需要讓每個每個依賴 SystemConfigService的 Bean 都改代碼加上註解,那有沒有一種預設就讓 SystemConfigService 提前的方法?
Spring 中 Bean 創建的相關知識
ConfigurationClassPostProcessor 的介紹
BeanDefinitionRegistryPostProcessor 相關介面的介紹
-
在 BeanFactory 初始化之後調用,來定製和修改 BeanFactory 的內容
-
所有的 Bean 定義(BeanDefinition)已經保存載入到 beanFactory,但是 Bean 的實例還未創建
-
方法的入參是 ConfigurrableListableBeanFactory,意思是你可以調整 ConfigurrableListableBeanFactory 的配置
-
是 BeanFactoryPostProcessor 的子介面
-
在所有 Bean 定義(BeanDefinition)信息將要被載入,Bean 實例還未創建的時候載入
-
優先於 BeanFactoryPostProcessor 執行,利用 BeanDefinitionRegistryPostProcessor 可以給 Spring 容器中自定義添加 Bean
-
方法入參是 BeanDefinitionRegistry,意思是你可以調整 BeanDefinitionRegistry 的配置
-
在 Bean 實例化之後執行的
-
執行順序在 BeanFactoryPostProcessor 之後
-
方法入參是 Object bean,意思是你可以調整 bean 的配置
最終答案
# 註冊 ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=com.antbank.demo.bootstrap.MyApplicationContextInitializer
註冊 ApplicationContextInitializer 的目的其實是為了接下來註冊 BeanDefinitionRegistryPostProcessor 到 Spring 中,我沒有找到直接使用 spring.factories 來註冊 BeanDefinitionRegistryPostProcessor 的方式,猜測是不支持的:
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 註意,如果你同時還使用了 spring cloud,這裡需要做個判斷,要不要在 spring cloud applicationContext 中做這個事
// 通常 spring cloud 中的 bean 都和業務沒關係,是需要跳過的
applicationContext.addBeanFactoryPostProcessor(new MyBeanDefinitionRegistryPostProcessor());
}
}
除了使用 spring 提供的 SPI 來註冊 ApplicationContextInitializer,你也可以用 SpringApplication.addInitializers 的方式直接在 main 方法中直接註冊一個 ApplicationContextInitializer 結果都是可以的:
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(SpringBootDemoApplication.class);
// 通過 SpringApplication 註冊 ApplicationContextInitializer
application.addInitializers(new MyApplicationContextInitializer());
application.run(args);
}
}
當然了,通過 Spring 的事件機制也可以做到註冊 BeanDefinitionRegistryPostProcessor,選擇實現合適的 ApplicationListener 事件,可以通過 ApplicationContextEvent 獲得 ApplicationContext,即可註冊 BeanDefinitionRegistryPostProcessor,這裡就不多展開了。
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 手動註冊一個 BeanDefinition
registry.registerBeanDefinition("systemConfigService", new RootBeanDefinition(SystemConfigService.class));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}
當然你也可以使用一個類同時實現 ApplicationContextInitializer 和BeanDefinitionRegistryPostProcessor
本文來自博客園,作者:古道輕風,轉載請註明原文鏈接:https://www.cnblogs.com/88223100/p/Spring-Boot-How-to-make-your-bean-complete-loading-before-other-beans.html