欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析Spring Boot 如何讓你的 bean 在其他 bean 之前完成加載

 更新時間:2024年01月30日 11:00:43   作者:Young丶  
在 SpringBoot 中如何讓自己的某個指定的 Bean 在其他 Bean 前完成被 Spring 加載?我聽到這個問題的第一反應是,為什么會有這樣奇怪的需求?下面小編給大家分析下Spring Boot 如何讓你的 bean 在其他 bean 之前完成加載 ,感興趣的朋友一起看看吧

問題

今天有個小伙伴給我出了一個難題:在 SpringBoot 中如何讓自己的某個指定的 Bean 在其他 Bean 前完成被 Spring 加載?我聽到這個問題的第一反應是,為什么會有這樣奇怪的需求?

Talk is cheap,show me the code,這里列出了那個想做最先加載的“天選 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 更新緩存的代碼
    // ...
}

看過了上面的代碼后,很容易就理解了為什么會標題中的需求了。

SystemConfigService 是一個提供了查詢系統屬性的服務,系統屬性存放在 DB 中并且讀多寫少,在 Bean 創(chuàng)建的時候,通過 @PostConstruct 注解的 init() 方法完成了數據加載到緩存中,最關鍵的是,由于是系統屬性,所以需要在很多地方都想使用,尤其需要在很多 bean 啟動的時候使用,為了方便就提供了 static 方法來方便調用,這樣其他的 bean 不需要依賴注入就可以直接調用,但問題是系統屬性是存在 db 里面的,這就導致了不能把 SystemConfigService做成一個純「工具類」,它必須要被 Spring 托管起來,完成 mapper 的注入才能正常工作。因此這樣一來就比較麻煩,其他的類或者 Bean 如果想安全的使用 SystemConfigService#getSystemConfig 中的獲取配置的靜態(tài)方法,就必須等 SystemConfigService 先被 Spring 創(chuàng)建加載起來,完成 init() 方法后才可以。

所以才有了最開頭提到的問題,如何讓這個 Bean 在其他的 Bean 之前加載。

SpringBoot 官方文檔推薦做法

這里引用了一段 Spring Framework 官方文檔的原文:

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.

可以看到 Spring 對于依賴注入更推薦(is preferable)使用構造函數來注入必須的依賴,用 setter 方法來注入可選的依賴。至于我們平時工作中更多采用的 @Autowired 注解 + 屬性的注入方式是不推薦的,這也是為什么你用 Idea 集成開發(fā)環(huán)境的時候會給你一個警告。

按照 Spring 的文檔,我們應該直接去掉 getSystemConfig 的 static 修飾,讓 getSystemConfig 變成一個實例方法,讓每個需要依賴的 SystemConfigService 的 Bean 通過構造函數完成依賴注入,這樣 Spring 會保證每個 Bean 在創(chuàng)建之前會先把它所有的依賴創(chuàng)建并初始化完成。

看來我們還是要想一些其他的方法來達成我們的目的。

嘗試解決問題的一些方法

@Order 注解或者實現 org.springframework.core.Ordered

最先想到的就是 Spring 提供的 Order 相關的注解和接口,實際上測試下來不可行。Order 相關的方法一般用來控制 Spring 自身組件相關 Bean 的順序,比如 ApplicationListener,RegistrationBean 等,對于我們自己使用 @Service @Compont 注解注冊的業(yè)務相關的 bean 沒有排序的效果。

@AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore 注解

測試下來這些注解也是不可行,它們和 Ordered 一樣都是針對 Spring 自身組件 Bean 的順序。

@DependsOn 注解

接下來是嘗試加上 @DependsOn 注解:

@Service
@DependsOn({"systemConfigService"})
public class BizService {
    public BizService() {
        String xxValue = SystemConfigService.getSystemConfig("xxKey");
        // 可行
    }
}

這樣測試下來是可以是可以的,就是操作起來也太麻煩了,需要讓每個每個依賴 SystemConfigService的 Bean 都改代碼加上注解,那有沒有一種默認就讓 SystemConfigService 提前的方法?

上面提到的方法都不好用,那我們只能利用 spring 給我們提供的擴展點來做文章了。

Spring 中 Bean 創(chuàng)建的相關知識

首先要明白一點,Bean 創(chuàng)建的順序是怎么來的,如果你對 Spring 的源碼比較熟悉,你會知道在 AbstractApplicationContext 里面有個 refresh 方法, Bean 創(chuàng)建的大部分邏輯都在 refresh 方法里面,在 refresh 末尾的 finishBeanFactoryInitialization(beanFactory) 方法調用中,會調用 beanFactory.preInstantiateSingletons(),在這里對所有的 beanDefinitionNames 一一遍歷,進行 bean 實例化和組裝:

這個 beanDefinitionNames 列表的順序就決定了 Bean 的創(chuàng)建順序,那么這個 beanDefinitionNames 列表又是怎么來的?答案是 ConfigurationClassPostProcessor 通過掃描你的代碼和注解生成的,將 Bean 掃描解析成 Bean 定義(BeanDefinition),同時將 Bean 定義(BeanDefinition)注冊到 BeanDefinitionRegistry 中,才有了 beanDefinitionNames 列表。

ConfigurationClassPostProcessor 的介紹

在 BeanFactory 初始化之后調用,來定制和修改 BeanFactory 的內容
所有的 Bean 定義(BeanDefinition)已經保存加載到 beanFactory,但是 Bean 的實例還未創(chuàng)建
方法的入參是 ConfigurrableListableBeanFactory,意思是你可以調整 ConfigurrableListableBeanFactory 的配置

BeanDefinitionRegistryPostProcessor 相關接口的介紹

接下來還要介紹 Spring 中提供的一些擴展,它們在 Bean 的創(chuàng)建過程中起到非常重要的作用。

BeanFactoryPostProcessor 它的作用:

  • 在 BeanFactory 初始化之后調用,來定制和修改 BeanFactory 的內容
  • 所有的 Bean 定義(BeanDefinition)已經保存加載到 beanFactory,但是 Bean 的實例還未創(chuàng)建
  • 方法的入參是 ConfigurrableListableBeanFactory,意思是你可以調整 ConfigurrableListableBeanFactory 的配置

BeanDefinitionRegistryPostProcessor 它的作用:

  • 是 BeanFactoryPostProcessor 的子接口
  • 在所有 Bean 定義(BeanDefinition)信息將要被加載,Bean 實例還未創(chuàng)建的時候加載
  • 優(yōu)先于 BeanFactoryPostProcessor 執(zhí)行,利用 BeanDefinitionRegistryPostProcessor 可以給 Spring 容器中自定義添加 Bean
  • 方法入參是 BeanDefinitionRegistry,意思是你可以調整 BeanDefinitionRegistry 的配置

還有一個類似的 BeanPostProcessor 它的作用:

  • 在 Bean 實例化之后執(zhí)行的
  • 執(zhí)行順序在 BeanFactoryPostProcessor 之后
  • 方法入參是 Object bean,意思是你可以調整 bean 的配置

搞明白了以上的內容,下面我們可以直接動手寫代碼了。

最終答案

第一步:通過 spring.factories 擴展來注冊一個 ApplicationContextInitializer:

# 注冊 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 都和業(yè)務沒關系,是需要跳過的
        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,這里就不多展開了。

這里需要注意一點,為什么需要用 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 自帶的優(yōu)先級要高,所以這里就不需要再實現 Ordered 接口提升優(yōu)先級就可以排在 ConfigurationClassPostProcessor 前面:

經過測試發(fā)現,上面的方式可行的,SystemConfigService 被排在第五個 Bean 進行實例化,排在前面的四個都是 Spring 自己內部的 Bean 了,也沒有必要再提前了。

本文提供的方式并不是唯一的,如果你有更好的方法,歡迎在評論區(qū)留言交流。

到此這篇關于Spring Boot 如何讓你的 bean 在其他 bean 之前完成加載 的文章就介紹到這了,更多相關Spring Boot bean 加載 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析

    SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析

    這篇文章主要介紹了SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08
  • Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程

    Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程

    這篇文章主要為大家詳細介紹了Eclipse下基于Java的OpenCV開發(fā)環(huán)境配置教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Java中Integer和int的使用及注意點

    Java中Integer和int的使用及注意點

    文章主要介紹了Java中Integer和Long類的緩存機制以及它們的比較方式,Integer和Long類在-128到127之間的值會被緩存,因此在這個范圍內的值比較時可以使用==運算符,而超出這個范圍的值則需要使用equals()方法進行比較
    2025-01-01
  • Mybatis基于注解與XML開發(fā)使用流程

    Mybatis基于注解與XML開發(fā)使用流程

    MyBatis是Java的持久化框架,目的是為了使操作數據庫更加方便、靈活、高效,可以通過Java注解和XML文件來映射Java對象和SQL語句,提供了非常靈活的SQL編寫方式和動態(tài)SQL語句的創(chuàng)建方式,這篇文章主要介紹了Mybatis基于注解與XML開發(fā),需要的朋友可以參考下
    2023-07-07
  • 一文帶你掌握SpringBoot中常見定時任務的實現

    一文帶你掌握SpringBoot中常見定時任務的實現

    這篇文章主要為大家詳細介紹了Spring?Boot中定時任務的基本用法、高級特性以及最佳實踐,幫助開發(fā)人員更好地理解和應用定時任務,提高系統的穩(wěn)定性和可靠性,需要的可以參考下
    2024-03-03
  • MyBatis-Plus枚舉和自定義主鍵ID的實現步驟

    MyBatis-Plus枚舉和自定義主鍵ID的實現步驟

    這篇文章主要給大家介紹了關于MyBatis-Plus枚舉和自定義主鍵ID的相關資料,文中通過實例代碼以及圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2022-02-02
  • 深入探究Java多線程并發(fā)編程的要點

    深入探究Java多線程并發(fā)編程的要點

    這篇文章主要介紹了深入探究Java多線程并發(fā)編程的要點,包括關鍵字synchronized的使用和wait()與notify()獲取對象鎖的三種方式,需要的朋友可以參考下
    2015-11-11
  • maven子模塊相互依賴打包時報錯找不到類的解決方案

    maven子模塊相互依賴打包時報錯找不到類的解決方案

    本文主要介紹了maven子模塊相互依賴打包時報錯找不到類的解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • SpringBoot與rabbitmq的結合的示例

    SpringBoot與rabbitmq的結合的示例

    這篇文章主要介紹了SpringBoot與rabbitmq的結合的示例,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • SpringBoot下實現session保持方式

    SpringBoot下實現session保持方式

    這篇文章主要介紹了SpringBoot下實現session保持方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評論