【SpringBoot基礎系列-實戰】如何指定 bean 最早加載(應用篇)

【基礎系列-實戰】如何指定 bean 最早加載(應用篇)java

在平常的業務開發中,絕大多數咱們都是不關注 bean 的加載順序,然而若是在某些場景下,當咱們但願某個 bean 優於其餘的 bean 被實例化時,每每並無咱們想象中的那麼簡單git

<!-- more -->github

I. 啓動類指定方式

在實際的 SpringBoot 開發中,咱們知道都會有一個啓動類,若是但願某個類被優先加載,一個成本最低的簡單實現,就是在啓動類裏添加上依賴spring

@SpringBootApplication
public class Application {

    public Application(DemoBean demoBean) {
        demoBean.print();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

請注意上面的構造方法,若是咱們但願在應用啓動以前,demoBean就已經被加載了,那就讓 Application 強制依賴它,因此再 Application 的 bean 初始化以前,確定會優先實例化demoBeanspringboot

相信上面這種寫法,你們並不會陌生,特別是當咱們應用啓動以後,發現某個依賴的 bean(通常來說是第三方庫提供的 bean)尚未初始化致使 npe 時,用這種方法仍是比較多的微信

case1app

咱們且不談這種實現方式是否優雅,當咱們但願targetBean在全部的 bean 實例化以前被實例時,上面這種寫法是否必定會生效呢?ide

case2spring-boot

中間件同窗:吭哧吭哧的開發了一個 🐂🍺jar 包,只要接入了保證你的應用永遠不會宕機(請無視誇張的言語),惟一的要求是接入時,須要優先加載 jar 包裏面的firstBean...工具

接入方:你的 bean 要求被首先加載這個得你本身保證啊,我寫些 if/else 代碼已經很辛苦了,哪有精力保證你的這個優先加載!!!你本身都無法保證,那我也沒辦法保證...

中間件同窗:還能不能愉快的玩耍了....

II. InstantiationAwareBeanPostProcessorAdapter方式

在看下文的實現以前,牆裂推薦先看一下博文: 【SpringBoot 基礎系列】指定 Bean 初始化順序的若干姿式

接下來介紹另一種使用姿式,藉助InstantiationAwareBeanPostProcessorAdapter來實如今 bean 實例化以前優先加載目標 bean

聲明

  • 我我的認爲下面這種使用方式,依然很不優雅,若有更好方式,懇請大佬留言告知
  • 我我的認爲下面這種使用方式,依然很不優雅,若有更好方式,懇請大佬留言告知
  • 我我的認爲下面這種使用方式,依然很不優雅,若有更好方式,懇請大佬留言告知

1. 場景分析

假設咱們提供了一個配置讀取的工具包,可是不一樣的應用可能對配置的存儲有不一樣的要求,好比有的配置存在本地,有的存在 db,有的經過 http 方式遠程獲取;而這些存儲方式呢,經過application.yml配置文件中的配置參數config.save.mode來指定

這個工具包呢,會作一件事情,掃描應用程序的全部類,並注入配置信息,因此咱們但願在應用程序啓動以前,這個工具包就已經從數據源獲取到了配置信息,而這又要求先獲取應用究竟是用的哪一個數據源

簡單來說,就是但願在應用程序工做以前,DatasourceLoader這個 bean 已經被實例化了

-- 插播一句,上面這個 case,正是我在籌備的SpringBoot實戰教程--從0到1建立一個高可用的配置中心的具體應用場景

2. 常規流程

新建一個 SpringBoot 項目工程,源碼中 springboot 版本爲2.2.1.RELEASE

首先咱們來定義這個目標 bean: DatasourceLoader

public class DatasourceLoader {

    @Getter
    private String mode;

    public DatasourceLoader(Environment environment) {
        this.mode = environment.getProperty("config.save.mode");
        System.out.println("init DatasourceLoader for:" + mode);
    }

    @PostConstruct
    public void loadResourcres() {
        System.out.println("開始初始化資源");
    }
}

由於這個工程主要是供第三方使用,因此按照 SpringBoot 的一般玩法,聲明一個自動配置類

@Configuration
public class ClientAutoConfiguration {
    @Bean
    public DatasourceLoader propertyLoader(Environment environment) {
        return new DatasourceLoader(environment);
    }
}

而後在資源目錄下新建文件夾 META-INF,建立文件spring.factories,內容以下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.git.hui.boot.client.ClientAutoConfiguration

而後使用方添加依賴,就完了???

上面這套流程,屬於通常的工具包寫法了,請注意,這種方式,通常狀況下是應用程序內聲明的 bean 加載完畢以後,纔會加載第三方依賴包中聲明的 bean;也就是說經過上面的寫法,DatasourceLoader並不會被優先加載,也達不到咱們的目的(應用都開始服務了,結果全部的配置都是 null)

3. 特殊寫法

接下來咱們藉助全部的 bean 在實例化以前,會優先檢測是否存在InstantiationAwareBeanPostProcessor接口這個特色,來實現DatasourceLoader的優先加載

public class ClientBeanProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
        }

        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
        // 經過主動調用beanFactory#getBean來顯示實例化目標bean
        DatasourceLoader propertyLoader = this.beanFactory.getBean(DatasourceLoader.class);
        System.out.println(propertyLoader);
    }
}

上面的實現比較簡單,藉助beanFactory#getBean來手動觸發 bean 的實例,經過實現BeanFactoryAware接口來獲取BeanFactory,由於實現InstantiationAwareBeanPostProcessor接口的類會優先於 Bean 被實例,以此來間接的達到咱們的目的

關於上面這一套流程分析, 請關注微信公衆號/我的博客站點,靜待源碼分析篇

接下來的問題就是如何讓它生效了,咱們這裏使用 Import 註解來實現

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({ClientAutoConfiguration.class, ClientBeanProcessor.class})
public @interface EnableOrderClient {
}

請注意上面的註解中,導入上面的自動配置類,和ClientBeanProcessor,因此上一節中的spring.factories文件能夠不須要哦

4. 測試

上面的主要流程就完事了,接下來就須要進入測試,咱們新建一個 SpringBoot 項目,添加依賴

先加一個 demoBean

@Component
public class DemoBean {

    public DemoBean() {
        System.out.println("demo bean init!");
    }

    public void print() {
        System.out.println("print demo bean ");
    }
}

而後是啓動類, @EnableOrderClient這個註解必須得有哦

@EnableOrderClient
@SpringBootApplication
public class Application {

    public Application(DemoBean demoBean) {
        demoBean.print();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

在咱們啓動以前,請猜想一下,DemoBeanDatasourceLoader這裏這兩個 bean,誰會優先被實例化?

下面是輸出結果

IMAGE

從上面的兩個紅框輸出,能夠知道咱們的啓動類指定方式依賴的 bean,並不必定會最早被加載哦

5. 小結

最後小結一下,本文提出了兩種讓 bean 優先加載的方式,一個是在啓動類的構造方法中添加依賴,一個是藉助InstantiationAwareBeanPostProcessorAdapter在 bean 實例化以前被建立的特色,結合BeanFactory來手動觸發目標 bean 的建立

最後經過@Import註解讓咱們的BeanPostProcessorAdapter生效

有知道其餘方式的大佬,請不吝賜教啊

II. 其餘

0. 項目

1. 一灰灰 Blog

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

一灰灰blog

相關文章
相關標籤/搜索