Spring Boot |如何让你的 bean 在其他 bean 之前完成加载
本文围绕 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