解決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的行爲了。那麼,問題如何解決呢?
java

    辦法有二:
app

  • 部分放棄依賴注入:當A每次須要B時,主動向容器請求新的Bean實例,便可保證每次注入的B都是最新的實例。框架

  • 利用方法注入。ide

    第一種方式顯然不是一個好的作法,代碼主動請求Bean實例,必然致使代碼與SpringAPI耦合在一塊兒,形成嚴重的代碼污染。一般狀況下,咱們會採用第二種作法。使用方法注入。
測試

    方法注入一般使用lookup方法注入,利用lookup方法注入可讓Spring容器重寫容器中Bean的抽象或具體方法,返回查找容器中其餘Bean的結果,被查找的Bean一般是一個non-singleton的Bean(儘管也能夠是一個singleton的Bean)。Spring經過使用CGLIB庫修改客戶端的二進制碼,從而實現上述要求。看下面的例子:spa

public class CellPhone implements Phone {
    public CellPhone() {
        System.out.println("Spring實例化依賴的Bean...CellPhone實例");
    }
    public return call() {
        return "正在打電話...";
    }
}

    上面的CellPhone將被部署成prototype的Bean,並被一個singleton的Bean所依賴。若是讓Spring容器直接將prototype的Bean注入到singleton中,就會出現上面的問題。爲了解決這個問題,咱們在singleton的Bean裏增長一個抽象方法,該方法的返回類型是一個被依賴的Bean——注意這個方法是一個抽象方法,由於程序中沒有爲該方法提供實現,這個實現過程由Spring完成。下面是該singleton做用域的Bean的代碼:
prototype

public abstract class Developer implements Person {
    public Developer() {
        System.out.println("Spring實例化主調的Bean...Developer實例");
    }
    //定義一個抽象方法,該方法將由Spring實現
    public abstract Phone getPhone();
    @Override
    public void call() {
        System.out.println("正在使用 " + getPhone() + " 打電話");
        System.out.println(getPhone().call());
    }
}

    上面的代碼定義了一個抽象的getPhone方法,一般狀況下,程序不能調用這個方法,但Spring框架將會負責爲該方法提供是先提,這樣這個方法就會變成具體方法了,程序也就能夠調用該方法了。爲了讓Spring知道如何實現該方法,咱們須要在配置文件中使用<lookup-method>標籤,這個標籤須要指定以下兩個屬性:code

  • name:指定須要讓Spring實現的方法xml

  • bean:指定Spring實現該方法後返回的值對象

    下面是配置片斷:

<!-- 將CellPhone部署成prototype的範圍 -->
<bean id="cellPhone" class="com.abc.CellPhone" scope="prototype" />
<bean id="developer" class="com.abc.Developer">
    <!-- getPhone方法返回CellPhone,每次調用將獲取新的CellPhone -->
    <lookup-method name="getPhone" bean="cellPhone" />
</bean>

    上面配置的<lookup-method>指定Spring將負責實現getPhone方法,該方法將返回容器中的prototype類型的cellPhone實例。下面是測試類:

public class Test {
    public static void main(String args[]) {
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Developer d = context.getBean("developer", Developer.class);
        d.call();
        d.call();
    }
}

    執行結果以下:

Spring實例化主調的Bean...Developer實例
Spring實例化依賴的Bean...CellPhone實例
正在使用 com.abc.CellPhone@3e12ad 打電話
Spring實例化依賴的Bean...CellPhone實例
正在打電話...
Spring實例化依賴的Bean...CellPhone實例
正在使用 com.abc.CellPhone@41af2e 打電話
Spring實例化依賴的Bean...CellPhone實例
正在打電話...

    結果代表:當lookup方法注入後,系統每次調用getPhone都會返回最新的CellPhone實例而非最先的CellPhone實例。

    注意:要保證lookup方法注入每次產生的Bean實例,必須將目標Bean(本例爲cellPhone)佈署成prototype做用域。不然,若是容器中只有一個目標Bean實例,即便採用lookup方法注入,每次依然返回同一個Bean實例。另外,lookup方法注入不只能用於設值注入,還能用於構造注入。

相關文章
相關標籤/搜索