開發過程中有這樣一個場景,2個 bean 初始化邏輯中有依賴關系,需要控制二者的初始化順序。實現方式可以有多種,本文結合目前對 spring 的理解,嘗試列出幾種思路。
場景
假設a,b兩個 bean 都需要在初始化的時候從本地磁盤讀取文件,其中b加載的文件,依賴a中加載的全局配置文件中配置的路徑,所以需要a先于b初始化,此外a中的配置改變后也需要觸發b的重新加載邏輯,所以a,b需要注入彼此。
對于下面的模型,問題簡化為:我們需要inita()先于initb()得到執行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
@service public class a { @autowired private b b; public a() { system.out.println( "a construct" ); } @postconstruct public void init() { inita(); } private void inita() { system.out.println( "a init" ); } } @service public class b { @autowired private a a; public b() { system.out.println( "b construct" ); } @postconstruct public void init() { initb(); } private void initb(){ system.out.println( "b init" ); } } |
方案一:立flag
我們可以在業務層自己控制a,b的初始化順序,在a中設置一個“是否初始化的”標記,b初始化前檢測a是否得以初始化,如果沒有則調用a的初始化方法,所謂的check-and-act。對于上述模型,實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
@service public class a { private static volatile boolean initialized; @autowired private b b; public a() { system.out.println( "a construct" ); } @postconstruct public void init() { inita(); } public boolean isinitialized() { return initialized; } public void inita() { if (!isinitialized()) { system.out.println( "a init" ); } initialized = true ; } } @service public class b { @autowired private a a; public b() { system.out.println( "b construct" ); } @postconstruct public void init() { initb(); } private void initb() { if (!a.isinitialized()) { a.inita(); } system.out.println( "b init" ); } |
執行效果:
a construct
b construct
a init
b init
這種立flag的方法好處是可以做到lazy initialization,但是如果類似邏輯很多的話代碼中到處充斥著類似代碼,不優雅,所以考慮是否框架本身就可以滿足我們的需要。
方案二:使用dependson
spring 中的 dependson 注解可以保證被依賴的bean先于當前bean被容器創建,但是如果不理解spring中bean加載過程會對 dependson 有誤解,自己也確實踩過坑。對于上述模型,如果在b上加上注解@dependson({"a"}),得到的執行結果是:
a construct
b construct
b init
a init
在這里問題的關鍵是:bean屬性的注入是在初始化方法調用之前。
1
2
3
4
5
6
7
|
// 代碼位置:abstractautowirecapablebeanfactory.docreatebean // 填充 bean 的各個屬性,包括依賴注入 populatebean(beanname, mbd, instancewrapper); if (exposedobject != null ) { // 調用初始化方法,如果是 initializingbean 則先調用 afterpropertiesset 然后調用自定義的init-method 方法 exposedobject = initializebean(beanname, exposedobject, mbd); } |
結合本例,發生的實際情況是,因為出現了循環依賴,a依賴b,加載b,b依賴a,所以得到了一個提前暴露的a,然后調用b的初始化方法,接著回到a的初始化方法。具體源碼分析過程如下:
applicationcontext 在 refresh 過程中的最后會加載所有的 no-lazy 單例。
本例中,先加載的bean a,最終通過無參構造器構造,然后,繼續屬性填充(populatebean),發現需要注入 bean b。所以轉而加載 bean b(遞歸調用 getbean())。此時發現 bean b 需要 dependson("a"),在保存依賴關系(為了防止循環 depends)后,調用 getbean("a"),此時會得到提前暴露的 bean a ,所以繼續 b 的加載,流程為: 初始化策略構造實例 -> 屬性填充(同樣會注入提前暴露的 bean a ) -> 調用初始化方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 代碼位置:abstractbeanfactory.dogetbean // guarantee initialization of beans that the current bean depends on. 實例化依賴的 bean string[] dependson = mbd.getdependson(); if (dependson != null ) { for (string dep : dependson) { if (isdependent(beanname, dep)) { throw new beancreationexception(mbd.getresourcedescription(), beanname, "circular depends-on relationship between '" + beanname + "' and '" + dep + "'" ); } registerdependentbean(dep, beanname); // 緩存 bean 依賴的關系 getbean(dep); } } |
得到提前暴露的 bean a的過程為:
此時此刻,bean a 的屬性注入完成了, 返回到調用初始化方法,所以表現的行為是:構造a -> 構造b -> b初始化 -> a初始化。
dependson只是保證的被依賴的bean先于當前bean被實例化,被創建,所以如果要采用這種方式實現bean初始化順序的控制,那么可以把初始化邏輯放在構造函數中,但是復雜耗時的邏輯仿造構造器中是不合適的,會影響系統啟動速度。
方案三:容器加載bean之前
spring 框架中很多地方都為我們提供了擴展點,很好的體現了開閉原則(ocp)。其中 beanfactorypostprocessor 可以允許我們在容器加載任何bean之前修改應用上下文中的beandefinition(從xml配置文件或者配置類中解析得到的bean信息,用于后續實例化bean)。
在本例中,就可以把a的初始化邏輯放在一個 beanfactorypostprocessor 中。
1
2
3
4
5
6
7
|
@component public class abeanfactorypostprocessor implements beanfactorypostprocessor { @override public void postprocessbeanfactory(configurablelistablebeanfactory configurablelistablebeanfactory) throws beansexception { a.inita(); } } |
執行效果:
a init
a construct
b construct
b init
這種方式把a中的初始化邏輯放到了加載bean之前,很適合加載系統全局配置,但是這種方式中初始化邏輯不能依賴bean的狀態。
方案四:事件監聽器的有序性
spring 中的 ordered 也是一個很重要的組件,很多邏輯中都會判斷對象是否實現了 ordered 接口,如果實現了就會先進行排序操作。比如在事件發布的時候,對獲取到的 applicationlistener 會先進行排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 代碼位置:abstractapplicationeventmulticaster.listenerretriever.getapplicationlisteners() public collection<applicationlistener<?>> getapplicationlisteners() { linkedlist<applicationlistener<?>> alllisteners = new linkedlist<applicationlistener<?>>(); for (applicationlistener<?> listener : this .applicationlisteners) { alllisteners.add(listener); } if (! this .applicationlistenerbeans.isempty()) { beanfactory beanfactory = getbeanfactory(); for (string listenerbeanname : this .applicationlistenerbeans) { try { applicationlistener<?> listener = beanfactory.getbean(listenerbeanname, applicationlistener. class ); if ( this .prefiltered || !alllisteners.contains(listener)) { alllisteners.add(listener); } } catch (nosuchbeandefinitionexception ex) { // singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } annotationawareordercomparator.sort(alllisteners); // 排序 return alllisteners; } |
所以可以利用事件監聽器在處理事件時的有序性,在應用上下文 refresh 完成后,分別實現a,b中對應的初始化邏輯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@component public class applicationlistenera implements applicationlistener<applicationcontextevent>, ordered { @override public void onapplicationevent(applicationcontextevent event) { inita(); } @override public int getorder() { return ordered.highest_precedence; // 比 applicationlistenerb 優先級高 } public static void inita() { system.out.println( "a init" ); } } @component public class applicationlistenerb implements applicationlistener<applicationcontextevent>, ordered{ @override public void onapplicationevent(applicationcontextevent event) { initb(); } @override public int getorder() { return ordered.highest_precedence - 1 ; } private void initb() { system.out.println( "b init" ); } } |
執行效果:
a construct
b construct
a init
b init
這種方式就是站在事件響應的角度,上下文加載完成后,先實現a邏輯,然后實現b邏輯。
總結
在平時的開發中使用的可能都是一個語言,一個框架的冰山一角,隨著對語言,對框架的不斷深入,你會發現更多的可能。本文只是基于目前對于 spring 框架的理解做出的嘗試,解決一個問題可能有多種方式,其中必然存在權衡選擇,取決于對業務對技術的理解。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://zhuanlan.zhihu.com/p/30112785?utm_source=tuicool&utm_medium=referral