亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

問題

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

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

/**
 * 系統(tǒng)屬性服務(wù)
**/
@Service
public class SystemConfigService {
    // 訪問 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 訪問 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)用,但問題是系統(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() 方法后才可以。

所以才有了最開頭提到的問題,如何讓這個(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 集成開發(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á)成我們的目的。

嘗試解決問題的一些方法

@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 沒有排序的效果。

@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 都改代碼加上注解,那有沒有一種默認(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 中,我沒有找到直接使用 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ù)沒關(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,這里就不多展開了。

這里需要注意一點(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 了,也沒有必要再提前了。

本文提供的方式并不是唯一的,如果你有更好的方法,歡迎在評(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)態(tài)配置郵箱發(fā)件人過(guò)程解析

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

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

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

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

    Java中Integer和int的使用及注意點(diǎn)

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

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

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

    一文帶你掌握SpringBoot中常見定時(shí)任務(wù)的實(shí)現(xiàn)

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

    MyBatis-Plus枚舉和自定義主鍵ID的實(shí)現(xiàn)步驟

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

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

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

    maven子模塊相互依賴打包時(shí)報(bào)錯(cuò)找不到類的解決方案

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

    SpringBoot與rabbitmq的結(jié)合的示例

    這篇文章主要介紹了SpringBoot與rabbitmq的結(jié)合的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • SpringBoot下實(shí)現(xiàn)session保持方式

    SpringBoot下實(shí)現(xiàn)session保持方式

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

最新評(píng)論