天天看點

解決Spring中singleton的Bean依賴于prototype的Bean的問題

    當spring容器中作用域不同的bean互相依賴時,可能出現一些問題,例如:一個作用域為singleton的bean(設為a)依賴于一個作用域為prototype的bean(設為b)。由于a是單例的,隻有一次初始化的機會,它的依賴關系也隻在初始化階段被設定,但它所依賴的b每次都會建立一個全新的執行個體,這将使a中的b不能及時得到更新。這樣将導緻如果用戶端多次請求a,并調用a中b的某個方法(或擷取a中b的某個屬性),服務端總是傳回同一個b,但用戶端直接請求b卻能獲得最新的對象,這就産生了對象不同步的情況。這樣就違背了b初衷:本來希望b具有prototype的行為,但是卻表現出singleton的行為了。那麼,問題如何解決呢?

    辦法有二:

部分放棄依賴注入:當a每次需要b時,主動向容器請求新的bean執行個體,即可保證每次注入的b都是最新的執行個體。

利用方法注入。

    第一種方式顯然不是一個好的做法,代碼主動請求bean執行個體,必然導緻代碼與springapi耦合在一起,造成嚴重的代碼污染。通常情況下,我們會采用第二種做法。使用方法注入。

    方法注入通常使用lookup方法注入,利用lookup方法注入可以讓spring容器重寫容器中bean的抽象或具體方法,傳回查找容器中其他bean的結果,被查找的bean通常是一個non-singleton的bean(盡管也可以是一個singleton的bean)。spring通過使用cglib庫修改用戶端的二進制碼,進而實作上述要求。看下面的例子:

<a href="http://my.oschina.net/itblog/blog/205860#">?</a>

1

2

3

4

5

6

7

8

<code>public</code> <code>class</code> <code>cellphone </code><code>implements</code> <code>phone {</code>

<code>    </code><code>public</code> <code>cellphone() {</code>

<code>        </code><code>system.out.println(</code><code>"spring執行個體化依賴的bean...cellphone執行個體"</code><code>);</code>

<code>    </code><code>}</code>

<code>    </code><code>public</code> <code>return</code> <code>call() {</code>

<code>        </code><code>return</code> <code>"正在打電話..."</code><code>;</code>

<code>}</code>

    上面的cellphone将被部署成prototype的bean,并被一個singleton的bean所依賴。如果讓spring容器直接将prototype的bean注入到singleton中,就會出現上面的問題。為了解決這個問題,我們在singleton的bean裡增加一個抽象方法,該方法的傳回類型是一個被依賴的bean——注意這個方法是一個抽象方法,因為程式中沒有為該方法提供實作,這個實作過程由spring完成。下面是該singleton作用域的bean的代碼:

9

10

11

12

<code>public</code> <code>abstract</code> <code>class</code> <code>developer </code><code>implements</code> <code>person {</code>

<code>    </code><code>public</code> <code>developer() {</code>

<code>        </code><code>system.out.println(</code><code>"spring執行個體化主調的bean...developer執行個體"</code><code>);</code>

<code>    </code><code>//定義一個抽象方法,該方法将由spring實作</code>

<code>    </code><code>public</code> <code>abstract</code> <code>phone getphone();</code>

<code>    </code><code>@override</code>

<code>    </code><code>public</code> <code>void</code> <code>call() {</code>

<code>        </code><code>system.out.println(</code><code>"正在使用 "</code> <code>+ getphone() + </code><code>" 打電話"</code><code>);</code>

<code>        </code><code>system.out.println(getphone().call());</code>

    上面的代碼定義了一個抽象的getphone方法,通常情況下,程式不能調用這個方法,但spring架構将會負責為該方法提供是先提,這樣這個方法就會變成具體方法了,程式也就可以調用該方法了。為了讓spring知道如何實作該方法,我們需要在配置檔案中使用&lt;lookup-method&gt;标簽,這個标簽需要指定如下兩個屬性:

name:指定需要讓spring實作的方法

bean:指定spring實作該方法後傳回的值

    下面是配置片段:

<code>&lt;!-- 将cellphone部署成prototype的範圍 --&gt;</code>

<code>&lt;</code><code>bean</code> <code>id</code><code>=</code><code>"cellphone"</code> <code>class</code><code>=</code><code>"com.abc.cellphone"</code> <code>scope</code><code>=</code><code>"prototype"</code> <code>/&gt;</code>

<code>&lt;</code><code>bean</code> <code>id</code><code>=</code><code>"developer"</code> <code>class</code><code>=</code><code>"com.abc.developer"</code><code>&gt;</code>

<code>    </code><code>&lt;!-- getphone方法傳回cellphone,每次調用将擷取新的cellphone --&gt;</code>

<code>    </code><code>&lt;</code><code>lookup-method</code> <code>name</code><code>=</code><code>"getphone"</code> <code>bean</code><code>=</code><code>"cellphone"</code> <code>/&gt;</code>

<code>&lt;/</code><code>bean</code><code>&gt;</code>

    上面配置的&lt;lookup-method&gt;指定spring将負責實作getphone方法,該方法将傳回容器中的prototype類型的cellphone執行個體。下面是測試類:

<code>public</code> <code>class</code> <code>test {</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string args[]) {</code>

<code>        </code><code>applicationcontext context = </code>

<code>                </code><code>new</code> <code>classpathxmlapplicationcontext(</code><code>"applicationcontext.xml"</code><code>);</code>

<code>        </code><code>developer d = context.getbean(</code><code>"developer"</code><code>, developer.</code><code>class</code><code>);</code>

<code>        </code><code>d.call();</code>

    執行結果如下:

<code>spring執行個體化主調的bean...developer執行個體</code>

<code>spring執行個體化依賴的bean...cellphone執行個體</code>

<code>正在使用 com.abc.cellphone</code><code>@3e12ad</code> <code>打電話</code>

<code>正在打電話...</code>

<code>正在使用 com.abc.cellphone</code><code>@41af2e</code> <code>打電話</code>

    結果表明:當lookup方法注入後,系統每次調用getphone都會傳回最新的cellphone執行個體而非最早的cellphone執行個體。

    注意:要保證lookup方法注入每次産生的bean執行個體,必須将目标bean(本例為cellphone)布署成prototype作用域。否則,如果容器中隻有一個目标bean執行個體,即使采用lookup方法注入,每次依然傳回同一個bean執行個體。另外,lookup方法注入不僅能用于設值注入,還能用于構造注入。