Spring如何控制Bean的載入順序

fuxing.發表於2024-05-09

前言

正常情況下,Spring 容器載入 Bean 的順序是不確定的,那麼我們如果需要按順序載入 Bean 時應如何操作?本文將詳細講述我們如何才能控制 Bean 的載入順序。




場景

我建立了 4 個 Class 檔案,分別命名為

  1. FirstInitialization
  2. SecondInitialization
  3. ThirdInitialization
  4. ForthInitialization

我希望這 4 個類按照 1、2、3、4 的順序載入。

如下圖,直接載入的話,順序是 1、4、2、3,並不能達到要求。

如何控制


注意:網上很多文章說Order註解或Ordered介面可以控制 Bean 的載入順序,其是並不能,它們的作用是定義 Spring IOC 容器中 Bean 定義類的執行順序的優先順序,並不是定義載入順序。


使用@DependsOn 註解

在需要調整順序的類上依次加@DependsOn註解,缺點是類過多的時候需要一個個加註解,且不好維護

@Component
public class FirstInitialization {

    @PostConstruct
    public void init(){
        System.out.println("我是第一個載入!");
    }

}
@Component
@DependsOn("firstInitialization")
public class SecondInitialization {

    @PostConstruct
    public void init(){
        System.out.println("我是第二個載入!");
    }

}
@Component
@DependsOn("secondInitialization")
public class ThirdInitialization {

    @PostConstruct
    public void init(){
        System.out.println("我是第三個載入!");
    }

}
@Component
@DependsOn("thirdInitialization")
public class ForthInitialization {

    @PostConstruct
    public void init(){
        System.out.println("我是第四個載入!");
    }

}

執行結果如下

基於 ApplicationContextInitializer 介面


介面簡介

這裡我簡單介紹一個這個介面的用處, 等到整理到相關原始碼的時候再詳細介紹。

ApplicationContextInitializer介面是在 Spring 容器重新整理之前執行的一個回撥函式。

執行時機:

  1. Spring 內部執行ConfigurableApplicationContext#refresh()方法前;
  2. SpringBoot 執行run()方法前。


一般有什麼用呢?

在 SpringBoot 應用中 Classpath 上會有很多 jar 包,有些 jar 包需要在refresh()呼叫前對應用上下文做一些初始化動作,因此會提供ApplicationContextInitializer介面的實現類,放在如下圖的檔案中,這樣會被SpringApplication#initialize發現,然後完成對應初始化。

實現步驟


首先建立一個類繼承ApplicationContextInitializer介面。

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

        //將自定義的BeanFactoryPostProcessor實現類儲存到ApplicationContext中
        applicationContext.addBeanFactoryPostProcessor(new MyBeanFactoryPostProcessor());
    }
}

建立`META-INF/spring.factories`檔案。


自定義`BeanDefinitionRegistryPostProcessor`。
/**
 * BeanFactoryPostProcessor的子類
 * 允許開發人員在Bean定義註冊之前和之後對BeanDefinition進行自定義處理,例如新增,修改或刪除Bean定義等。
 */
public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    // 初始化需要排序的類,這裡要保證插入順序只能用LinkedHashMap
    private static final Map<String, Class> ORDER_BEAN_MAP = new LinkedHashMap<>() {
        {
            put("firstInitialization", FirstInitialization.class);
            put("secondInitialization", SecondInitialization.class);
            put("thirdInitialization", ThirdInitialization.class);
            put("forthInitialization", ForthInitialization.class);
        }
    };

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        Optional.ofNullable(ORDER_BEAN_MAP.keySet()).orElse(new HashSet<>()).stream()
                .forEach(beanName -> {
                    // 初始化一個 Bean 定義
                    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                            .genericBeanDefinition().getBeanDefinition();

                    // 按順序註冊每個Bean
                    beanDefinition.setBeanClass(ORDER_BEAN_MAP.get(beanName));
                    registry.registerBeanDefinition(beanName, beanDefinition);
                });
    }
}

執行結果如下

相關文章