本文围绕 Spring Boot 中如何让你的 bean 在其他 bean 之前完成加载展开讨论。
问题
/** * 系统属性服务**/@Servicepublic class SystemConfigService { // 访问 db 的 mapper private final SystemConfigMapper systemConfigMapper; // 存放一些系统配置的缓存 map private static Map> 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 的配置
最终答案
# 注册 ApplicationContextInitializerorg.springframework.context.ApplicationContextInitializer=com.antbank.demo.bootstrap.MyApplicationContextInitializer
注册 ApplicationContextInitializer 的目的其实是为了接下来注册 BeanDefinitionRegistryPostProcessor 到 Spring 中,我没有找到直接使用 spring.factories 来注册 BeanDefinitionRegistryPostProcessor 的方式,猜测是不支持的:
public class MyApplicationContextInitializer implements ApplicationContextInitializer { @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 结果都是可以的:
@SpringBootApplicationpublic 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