在日常的業務開發中,絕大多數我們都是不關注 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);
}
}
在我們啟動之前,請猜測一下,DemoBean
和DatasourceLoader
這里這兩個 bean,誰會優先被實例化?
下面是輸出結果
從上面的兩個紅框輸出,可以知道我們的啟動類指定方式依賴的 bean,並不一定會最先被加載哦
5. 小結
最后小結一下,本文提出了兩種讓 bean 優先加載的方式,一個是在啟動類的構造方法中添加依賴,一個是借助InstantiationAwareBeanPostProcessorAdapter
在 bean 實例化之前被創建的特點,結合BeanFactory
來手動觸發目標 bean 的創建
最后通過@Import
注解讓我們的BeanPostProcessorAdapter
生效
有知道其他方式的大佬,請不吝賜教啊
II. 其他
0. 項目
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top