在平常的業務開發中,絕大多數咱們都是不關注 bean 的加載順序,然而若是在某些場景下,當咱們但願某個 bean 優於其餘的 bean 被實例化時,每每並無咱們想象中的那麼簡單git
<!-- more -->github
在實際的 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 初始化以前,確定會優先實例化demoBean
springboot
相信上面這種寫法,你們並不會陌生,特別是當咱們應用啓動以後,發現某個依賴的 bean(通常來說是第三方庫提供的 bean)尚未初始化致使 npe 時,用這種方法仍是比較多的微信
case1app
咱們且不談這種實現方式是否優雅,當咱們但願targetBean
在全部的 bean 實例化以前被實例時,上面這種寫法是否必定會生效呢?ide
case2spring-boot
中間件同窗:吭哧吭哧的開發了一個 🐂🍺jar 包,只要接入了保證你的應用永遠不會宕機(請無視誇張的言語),惟一的要求是接入時,須要優先加載 jar 包裏面的firstBean
...工具
接入方:你的 bean 要求被首先加載這個得你本身保證啊,我寫些 if/else 代碼已經很辛苦了,哪有精力保證你的這個優先加載!!!你本身都無法保證,那我也沒辦法保證...
中間件同窗:還能不能愉快的玩耍了....
InstantiationAwareBeanPostProcessorAdapter
方式在看下文的實現以前,牆裂推薦先看一下博文: 【SpringBoot 基礎系列】指定 Bean 初始化順序的若干姿式
接下來介紹另一種使用姿式,藉助InstantiationAwareBeanPostProcessorAdapter
來實如今 bean 實例化以前優先加載目標 bean
聲明
假設咱們提供了一個配置讀取的工具包,可是不一樣的應用可能對配置的存儲有不一樣的要求,好比有的配置存在本地,有的存在 db,有的經過 http 方式遠程獲取;而這些存儲方式呢,經過application.yml
配置文件中的配置參數config.save.mode
來指定
這個工具包呢,會作一件事情,掃描應用程序的全部類,並注入配置信息,因此咱們但願在應用程序啓動以前,這個工具包就已經從數據源獲取到了配置信息,而這又要求先獲取應用究竟是用的哪一個數據源
簡單來說,就是但願在應用程序工做以前,DatasourceLoader
這個 bean 已經被實例化了
-- 插播一句,上面這個 case,正是我在籌備的SpringBoot實戰教程--從0到1建立一個高可用的配置中心
的具體應用場景
新建一個 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)
接下來咱們藉助全部的 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
文件能夠不須要哦
上面的主要流程就完事了,接下來就須要進入測試,咱們新建一個 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); } }
在咱們啓動以前,請猜想一下,DemoBean
和DatasourceLoader
這裏這兩個 bean,誰會優先被實例化?
下面是輸出結果
從上面的兩個紅框輸出,能夠知道咱們的啓動類指定方式依賴的 bean,並不必定會最早被加載哦
最後小結一下,本文提出了兩種讓 bean 優先加載的方式,一個是在啓動類的構造方法中添加依賴,一個是藉助InstantiationAwareBeanPostProcessorAdapter
在 bean 實例化以前被建立的特色,結合BeanFactory
來手動觸發目標 bean 的建立
最後經過@Import
註解讓咱們的BeanPostProcessorAdapter
生效
有知道其餘方式的大佬,請不吝賜教啊
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛