最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

問(wèn)題

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

Talk is cheap,show me the code,這里列出了那個(gè)想做最先加載的“天選 Bean” 的代碼,我們來(lái)分析一下:

/**
 * 系統(tǒng)屬性服務(wù)
**/
@Service
public class SystemConfigService {
    // 訪問(wèn) db 的 mapper
    private final SystemConfigMapper systemConfigMapper;
    // 存放一些系統(tǒng)配置的緩存 map
    private static Map<String, String>> SYS_CONF_CACHE = new HashMap<>()
    // 使用構(gòu)造方法完成依賴注入
    public SystemConfigServiceImpl(SystemConfigMapper systemConfigMapper) {
        this.systemConfigMapper = systemConfigMapper;
    }
    // Bean 的初始化方法,撈取數(shù)據(jù)庫(kù)中的數(shù)據(jù),放入緩存的 map 中
    @PostConstruct
    public void init() {
        // systemConfigMapper 訪問(wèn) DB,撈取數(shù)據(jù)放入緩存的 map 中
        // SYS_CONF_CACHE.put(key, value);
        // ...
    }
    // 對(duì)外提供獲得系統(tǒng)配置的 static 工具方法
    public static String getSystemConfig(String key) {
        return SYS_CONF_CACHE.get(key);
    }
    // 省略了從 DB 更新緩存的代碼
    // ...
}

看過(guò)了上面的代碼后,很容易就理解了為什么會(huì)標(biāo)題中的需求了。

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

所以才有了最開(kāi)頭提到的問(wèn)題,如何讓這個(gè) 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 對(duì)于依賴注入更推薦(is preferable)使用構(gòu)造函數(shù)來(lái)注入必須的依賴,用 setter 方法來(lái)注入可選的依賴。至于我們平時(shí)工作中更多采用的 @Autowired 注解 + 屬性的注入方式是不推薦的,這也是為什么你用 Idea 集成開(kāi)發(fā)環(huán)境的時(shí)候會(huì)給你一個(gè)警告。

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

看來(lái)我們還是要想一些其他的方法來(lái)達(dá)成我們的目的。

嘗試解決問(wèn)題的一些方法

@Order 注解或者實(shí)現(xiàn) org.springframework.core.Ordered

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

@AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore 注解

測(cè)試下來(lái)這些注解也是不可行,它們和 Ordered 一樣都是針對(duì) Spring 自身組件 Bean 的順序。

@DependsOn 注解

接下來(lái)是嘗試加上 @DependsOn 注解:

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

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

上面提到的方法都不好用,那我們只能利用 spring 給我們提供的擴(kuò)展點(diǎn)來(lái)做文章了。

Spring 中 Bean 創(chuàng)建的相關(guān)知識(shí)

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

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

ConfigurationClassPostProcessor 的介紹

在 BeanFactory 初始化之后調(diào)用,來(lái)定制和修改 BeanFactory 的內(nèi)容
所有的 Bean 定義(BeanDefinition)已經(jīng)保存加載到 beanFactory,但是 Bean 的實(shí)例還未創(chuàng)建
方法的入?yún)⑹?ConfigurrableListableBeanFactory,意思是你可以調(diào)整 ConfigurrableListableBeanFactory 的配置

BeanDefinitionRegistryPostProcessor 相關(guān)接口的介紹

接下來(lái)還要介紹 Spring 中提供的一些擴(kuò)展,它們?cè)?Bean 的創(chuàng)建過(guò)程中起到非常重要的作用。

BeanFactoryPostProcessor 它的作用:

  • 在 BeanFactory 初始化之后調(diào)用,來(lái)定制和修改 BeanFactory 的內(nèi)容
  • 所有的 Bean 定義(BeanDefinition)已經(jīng)保存加載到 beanFactory,但是 Bean 的實(shí)例還未創(chuàng)建
  • 方法的入?yún)⑹?ConfigurrableListableBeanFactory,意思是你可以調(diào)整 ConfigurrableListableBeanFactory 的配置

BeanDefinitionRegistryPostProcessor 它的作用:

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

還有一個(gè)類似的 BeanPostProcessor 它的作用:

  • 在 Bean 實(shí)例化之后執(zhí)行的
  • 執(zhí)行順序在 BeanFactoryPostProcessor 之后
  • 方法入?yún)⑹?Object bean,意思是你可以調(diào)整 bean 的配置

搞明白了以上的內(nèi)容,下面我們可以直接動(dòng)手寫代碼了。

最終答案

第一步:通過(guò) spring.factories 擴(kuò)展來(lái)注冊(cè)一個(gè) ApplicationContextInitializer:

# 注冊(cè) ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=com.antbank.demo.bootstrap.MyApplicationContextInitializer

注冊(cè) ApplicationContextInitializer 的目的其實(shí)是為了接下來(lái)注冊(cè) BeanDefinitionRegistryPostProcessor 到 Spring 中,我沒(méi)有找到直接使用 spring.factories 來(lái)注冊(cè) BeanDefinitionRegistryPostProcessor 的方式,猜測(cè)是不支持的:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 注意,如果你同時(shí)還使用了 spring cloud,這里需要做個(gè)判斷,要不要在 spring cloud applicationContext 中做這個(gè)事
        // 通常 spring cloud 中的 bean 都和業(yè)務(wù)沒(méi)關(guān)系,是需要跳過(guò)的
        applicationContext.addBeanFactoryPostProcessor(new MyBeanDefinitionRegistryPostProcessor());
    }
}

除了使用 spring 提供的 SPI 來(lái)注冊(cè) ApplicationContextInitializer,你也可以用 SpringApplication.addInitializers 的方式直接在 main 方法中直接注冊(cè)一個(gè) ApplicationContextInitializer 結(jié)果都是可以的:

@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBootDemoApplication.class);
        // 通過(guò) SpringApplication 注冊(cè) ApplicationContextInitializer
        application.addInitializers(new MyApplicationContextInitializer());
        application.run(args);
    }
}

當(dāng)然了,通過(guò) Spring 的事件機(jī)制也可以做到注冊(cè) BeanDefinitionRegistryPostProcessor,選擇實(shí)現(xiàn)合適的 ApplicationListener 事件,可以通過(guò) ApplicationContextEvent 獲得 ApplicationContext,即可注冊(cè) BeanDefinitionRegistryPostProcessor,這里就不多展開(kāi)了。

這里需要注意一點(diǎn),為什么需要用 ApplicationContextInitializer 來(lái)注冊(cè) BeanDefinitionRegistryPostProcessor,能不能用 @Component 或者其他的注解的方式注冊(cè)?

答案是不能的。@Component 注解的方式注冊(cè)能注冊(cè)上的前提是能被 ConfigurationClassPostProcessor 掃描到,也就是說(shuō)用 @Component 注解的方式來(lái)注冊(cè),注冊(cè)出來(lái)的 Bean 一定不可能排在 ConfigurationClassPostProcessor 前面,而我們的目的就是在所有的 Bean 掃描前注冊(cè)你需要的 Bean,這樣才能排在其他所有 Bean 前面,所以這里的場(chǎng)景下是不能用注解注冊(cè)的,這點(diǎn)需要額外注意。

第二步:實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor,注冊(cè)目標(biāo) bean:

用 MyBeanDefinitionRegistryPostProcessor 在 ConfigurationClassPostProcessor 掃描前注冊(cè)你需要的目標(biāo) bean 的 BeanDefinition 即可。

public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 手動(dòng)注冊(cè)一個(gè) BeanDefinition
        registry.registerBeanDefinition("systemConfigService", new RootBeanDefinition(SystemConfigService.class));
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

當(dāng)然你也可以使用一個(gè)類同時(shí)實(shí)現(xiàn) ApplicationContextInitializer 和BeanDefinitionRegistryPostProcessor

通過(guò) applicationContext#addBeanFactoryPostProcessor 注冊(cè)的 BeanDefinitionRegistryPostProcessor,比 Spring 自帶的優(yōu)先級(jí)要高,所以這里就不需要再實(shí)現(xiàn) Ordered 接口提升優(yōu)先級(jí)就可以排在 ConfigurationClassPostProcessor 前面:

經(jīng)過(guò)測(cè)試發(fā)現(xiàn),上面的方式可行的,SystemConfigService 被排在第五個(gè) Bean 進(jìn)行實(shí)例化,排在前面的四個(gè)都是 Spring 自己內(nèi)部的 Bean 了,也沒(méi)有必要再提前了。

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

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

相關(guān)文章

  • 一文帶你了解SpringBoot的啟動(dòng)原理

    一文帶你了解SpringBoot的啟動(dòng)原理

    大家通常只需要給一個(gè)類添加一個(gè)@SpringBootApplication 注解,然后再加一個(gè)main 方法里面固定的寫法 SpringApplication.run(Application.class, args);那么spring boot 到底是如何啟動(dòng)服務(wù)的呢,接下來(lái)咱們通過(guò)源碼解析,需要的朋友可以參考下
    2023-05-05
  • IDEA “Cannot resolve symbol”爆紅問(wèn)題解決

    IDEA “Cannot resolve symbol”爆紅問(wèn)題解決

    最近發(fā)現(xiàn)個(gè)問(wèn)題,IDEA 無(wú)法識(shí)別同一個(gè) package 里的其他類,將其顯示為紅色,本文就來(lái)介紹一下IDEA “Cannot resolve symbol”爆紅問(wèn)題解決,感興趣的可以了解一下
    2023-10-10
  • 如何實(shí)現(xiàn)自定義SpringBoot的Starter組件

    如何實(shí)現(xiàn)自定義SpringBoot的Starter組件

    這篇文章主要介紹了實(shí)現(xiàn)自定義SpringBoot的Starter組件的示例代碼,想要自定義starter組件,首先要了解springboot是如何加載starter的,也就是springboot的自動(dòng)裝配機(jī)制原理,本文結(jié)合示例代碼詳細(xì)講解,需要的朋友可以參考下
    2023-02-02
  • Java 8實(shí)現(xiàn)任意參數(shù)的單鏈表

    Java 8實(shí)現(xiàn)任意參數(shù)的單鏈表

    這篇文章主要為大家詳細(xì)介紹了Java 8實(shí)現(xiàn)任意參數(shù)的單鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • SpringBoot項(xiàng)目部署在weblogic中間件的注意事項(xiàng)說(shuō)明

    SpringBoot項(xiàng)目部署在weblogic中間件的注意事項(xiàng)說(shuō)明

    這篇文章主要介紹了SpringBoot項(xiàng)目部署在weblogic中間件的注意事項(xiàng)說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 淺談Java方法調(diào)用的優(yōu)先級(jí)問(wèn)題

    淺談Java方法調(diào)用的優(yōu)先級(jí)問(wèn)題

    這篇文章主要介紹了淺談Java方法調(diào)用的優(yōu)先級(jí)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • 詳解Java引用類型的參數(shù)也是值傳遞

    詳解Java引用類型的參數(shù)也是值傳遞

    這篇文章主要介紹了Java引用類型的參數(shù)也是值傳遞,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • java數(shù)據(jù)隨機(jī)分頁(yè)實(shí)現(xiàn)方案

    java數(shù)據(jù)隨機(jī)分頁(yè)實(shí)現(xiàn)方案

    本文主要介紹了java數(shù)據(jù)隨機(jī)分頁(yè)實(shí)現(xiàn)方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java使用ArrayList實(shí)現(xiàn)撲克牌的示例代碼

    Java使用ArrayList實(shí)現(xiàn)撲克牌的示例代碼

    學(xué)習(xí)了關(guān)于集合類的知識(shí),我們可以做一個(gè)小項(xiàng)目來(lái)加深對(duì)集合類知識(shí)的學(xué)習(xí)!本文就來(lái)利用ArrayList實(shí)現(xiàn)撲克牌發(fā)牌洗牌效果,需要的可以參考一下
    2022-10-10
  • Java中ArrayList的使用詳細(xì)介紹

    Java中ArrayList的使用詳細(xì)介紹

    這篇文章主要介紹了Java中ArrayList的使用,本文給大家詳細(xì)講述該相關(guān)的知識(shí)點(diǎn),并且會(huì)通過(guò)大量的案例加以說(shuō)明,需要的朋友可以參考一下
    2022-04-04

最新評(píng)論

内乡县| 贵南县| 屯门区| 葫芦岛市| 建平县| 青冈县| 贡嘎县| 岱山县| 长春市| 清涧县| 兴仁县| 叙永县| 平果县| 丹江口市| 阿坝县| 阳谷县| 大兴区| 工布江达县| 华容县| 荥阳市| 吴江市| 滕州市| 交城县| 彭泽县| 屏山县| 历史| 永靖县| 叙永县| 元朗区| 巴彦淖尔市| 永胜县| 绥棱县| 宁波市| 靖宇县| 永清县| 津南区| 防城港市| 大同县| 巴楚县| 婺源县| 会理县|