無狀態 Bean 的作用域一般可以配置為 singleton(單例模式),如果我們往 singleton 的 Boss 中注入 prototype 的 Car,並希望每次調用 boss Bean 的 getCar() 方法時都能夠返回一個新的 car Bean,使用傳統的注入方式將無法實現這樣的要求。因為 singleton 的 Bean 注入關聯 Bean 的動作僅有一次,雖然 car Bean 的作用范圍是 prototype 類型,但 Boss 通過 getCar() 方法返回的對象還是最開始注入的那個 car Bean。
如果希望每次調用 getCar() 方法都返回一個新的 car Bean 的實例,一種可選的方法就是讓 Boss 實現 BeanFactoryAware 接口,且能夠訪問容器的引用,這樣 Boss 的 getCar() 方法就可以采取以下實現方式來達到目的:
public Car getCar(){ //通過 getBean() 返回 prototype 的 Bean,每次都將返回新實例 return (Car)factory.getBean("car"); }
但這種依賴 Spring 框架接口的設計將應用與 Spring 框架綁定在一起,部分開發者可能並不喜歡。針對前面提出的需求,是否有既不與 Spring 框架綁定,又可享受依賴注入好處的實現方案?Spring 沒有讓我們失望,可以通過方法注入的方案完美地解決這個問題。
1.lookup 方法注入
Spring loc 容器擁有復寫 Bean 方法的能力,這項魔術般的功能歸功於 CGLib 類包。CGLib 可以在運行期動態操作 Class 字節碼,為 Bean 動態創建子類或實現類。關於 CGLib 的進一步介紹,將在以后的文章中介紹。
現在聲明一個 MagicBoss 接口,並聲明一個 getCar() 的接口方法。
public interface MagicBoss { Car getCar(); }
下面不編寫任何實現類,僅通過配置為該接口提供動態的實現,讓 getCar() 接口方法每次都返回新的 car Bean。
<!--①prototype類型的Bean--> <bean id="car" class="com.smart.injectfun.Car" p:brand="紅旗CA72" p:price="2000" scope="prototype"/> <!--②實施方法注入--> <bean id="magicBoss" class="com.smart.injectfun.MagicBoss" > <lookup-method name="getCar" bean="car"/> </bean>
通過 lookup-method 元素標簽為 MagicBoss 的 getCar() 提供動態實現,返回 prototype 類型的 car Bean,這樣 Spring 將在運行期為 MagicBoss 接口提供動態實現,其效果等同於:
public class InjectFunTest { public ApplicationContext factory = null; private static String[] CONFIG_FILES = { "com/smart/injectfun/beans.xml" }; @BeforeMethod public void setUp() throws Exception { factory = new ClassPathXmlApplicationContext(CONFIG_FILES); } @Test public void testLookup(){ MagicBoss mboss = (MagicBoss) factory.getBean("magicBoss"); assertNotSame(mboss.getCar(),mboss.getCar()); } }
因為每次調用 MagicBoss 的 getCar() 方法都會從容器中獲取 car Bean,由於 car Bean 的作用域為 prototype,所以每次都返回新的 car 實例。
如果將 car Bean 的作用域設置為 singleton,雖然以上配置仍然可以運行,但這時 lookup 所提供的方法注入就沒有什么意義了。因為我們可以容易地編寫一個 MagicBoss 接口實現類,用屬性注入的方式達到相同的目的。所以 lookup 方法注入是有一定使用范圍的,一般在希望通過一個 singleton Bean 獲取一個 prototype Bean 時使用。
提示:由於方法注入時 Spring 需要用到 CGLib 類包,所以需要將 CGLib 類包加入到類路徑中,否則無法使用方法注入的功能。
2.方法替換
在金庸筆下,“乾坤大挪移”是明教至高無上的神功,在《倚天屠龍記》里會九陽神功的張無忌最終修成了正果。在 Spring IOC 容器里,用戶同樣可以擁有這種“乾坤大挪移”的能力:可以使用某個 Bean 的方法去替換另一個 Bean 的方法。
在下面的例子中,Boss1的 getCar() 方法返回一輛寶馬Z4。
public class Boss1 implements MagicBoss{ public Car getCar() { Car car = new Car(); car.setBrand("寶馬Z4"); return car; } }
Boss2 實現了 Spring 的 org.springframework.beans.factory.support.MethodReplacer 接口,在接口方法 reimplement() 中,返回一輛美人豹。
package com.smart.injectfun; import java.lang.reflect.Method; import org.springframework.beans.factory.support.MethodReplacer;
public class Boss2 implements MethodReplacer { public Object reimplement(Object arg0, Method arg1, Object[] arg2) throws Throwable { Car car = new Car(); car.setBrand("美人豹"); return car; } }
用於替換他人的 Bean 必須實現 MethodReplacer 接口,Spring 將利用該接口的方法去替換目標 Bean 的方法。下面通過 Spring loc 容器的“乾坤大挪移”術,用 Boss2 的方法去替換 Boss1 的 getCar() 方法。
<bean id="boss2" class="com.smart.injectfun.Boss2"/> <bean id="boss1" class="com.smart.injectfun.Boss1"> <replaced-method name="getCar" replacer="boss2"></replaced-method> </bean>
當從容器中返回 boss1 Bean 並調用其 getCar() 方法時,將返回一輛“美人豹”的car,調包成功。
但這種高級功能就像《宋玉答楚王問》中所說的陽春白雪一樣,在實際應用中很少使用,而屬性注入、構造函數注入等“下里巴人”式的普通功能反而在實際項目中使用最多。