作者:微信小助手
发布时间:2023-05-07T21:27:09
问题
/** * 系统属性服务**/@Servicepublic 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 的实例还未创建
是 BeanFactoryPostProcessor 的子接口
在所有 Bean 定义(BeanDefinition)信息将要被加载,Bean 实例还未创建的时候加载
优先于 BeanFactoryPostProcessor 执行,利用 BeanDefinitionRegistryPostProcessor 可以给 Spring 容器中自定义添加 Bean
在 Bean 实例化之后执行的
执行顺序在 BeanFactoryPostProcessor 之后
最终答案
# 注册 ApplicationContextInitializerorg.springframework.context.ApplicationContextInitializer=com.antbank.demo.bootstrap.MyApplicationContextInitializer
注册 ApplicationContextInitializer 的目的其实是为了接下来注册 BeanDefinitionRegistryPostProcessor 到 Spring 中,我没有找到直接使用 spring.factories 来注册 BeanDefinitionRegistryPostProcessor 的方式,猜测是不支持的:
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic 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 注册 ApplicationContextInitializerapplication.addInitializers(new MyApplicationContextInitializer());application.run(args);}}
当然了,通过 Spring 的事件机制也可以做到注册 BeanDefinitionRegistryPostProcessor,选择实现合适的 ApplicationListener 事件,可以通过 ApplicationContextEvent 获得 ApplicationContext,即可注册 BeanDefinitionRegistryPostProcessor,这里就不多展开了。
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// 手动注册一个 BeanDefinitionregistry.registerBeanDefinition("systemConfigService", new RootBeanDefinition(SystemConfigService.class));}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}}
当然你也可以使用一个类同时实现 ApplicationContextInitializer 和BeanDefinitionRegistryPostProcessor