首页  编辑  

SpringBoot设置Bean的加载顺序

Tags: /Java/   Date Created:
今天有个小伙伴给我出了一个难题:在 SpringBoot 中如何让自己的某个指定的 Bean 在其他 Bean 前完成被 Spring 加载?

@DependsOn 注解

接下来是尝试加上 @DependsOn 注解:
@Service@DependsOn({"systemConfigService"})public class BizService {
public BizService() { String xxValue = SystemConfigService.getSystemConfig("xxKey"); // 可行 }}

最终答案

第一步:通过 spring.factories 扩展来注册一个 ApplicationContextInitializer:
# 注册 ApplicationContextInitializerorg.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 结果都是可以的:

@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,这里就不多展开了。

这里需要注意一点,为什么需要用 ApplicationContextInitializer 来注册 BeanDefinitionRegistryPostProcessor,能不能用 @Component 或者其他的注解的方式注册?
答案是不能的。@Component 注解的方式注册能注册上的前提是能被 ConfigurationClassPostProcessor 扫描到,也就是说用 @Component 注解的方式来注册,注册出来的 Bean 一定不可能排在 ConfigurationClassPostProcessor 前面,而我们的目的就是在所有的 Bean 扫描前注册你需要的 Bean,这样才能排在其他所有 Bean 前面,所以这里的场景下是不能用注解注册的,这点需要额外注意。
第二步:实现 BeanDefinitionRegistryPostProcessor,注册目标 bean:
用 MyBeanDefinitionRegistryPostProcessor 在 ConfigurationClassPostProcessor 扫描前注册你需要的目标 bean 的 BeanDefinition 即可。
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

通过 applicationContext#addBeanFactoryPostProcessor 注册的 BeanDefinitionRegistryPostProcessor,比 Spring 自带的优先级要高,所以这里就不需要再实现 Ordered 接口提升优先级就可以排在 ConfigurationClassPostProcessor 前面:
经过测试发现,上面的方式可行的,SystemConfigService 被排在第五个 Bean 进行实例化,排在前面的四个都是 Spring 自己内部的 Bean 了,也没有必要再提前了。