當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方法注入不只能用於設值注入,還能用於構造注入。