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


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

在日常的業務開發中,絕大多數我們都是不關注 bean 的加載順序,然而如果在某些場景下,當我們希望某個 bean 優於其他的 bean 被實例化時,往往並沒有我們想象中的那么簡單

I. 啟動類指定方式

在實際的 SpringBoot 開發中,我們知道都會有一個啟動類,如果希望某個類被優先加載,一個成本最低的簡單實現,就是在啟動類里添加上依賴

@SpringBootApplication
public class Application {

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

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

請注意上面的構造方法,如果我們希望在應用啟動之前,demoBean就已經被加載了,那就讓 Application 強制依賴它,所以再 Application 的 bean 初始化之前,肯定會優先實例化demoBean

相信上面這種寫法,大家並不會陌生,特別是當我們應用啟動之后,發現某個依賴的 bean(一般來講是第三方庫提供的 bean)還沒有初始化導致 npe 時,用這種方法還是比較多的

case1

我們且不談這種實現方式是否優雅,當我們希望targetBean在所有的 bean 實例化之前被實例時,上面這種寫法是否一定會生效呢?

case2

中間件同學:吭哧吭哧的開發了一個 🐂🍺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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM