Spring-方法注入lookup、方法替換MethodReplacer介面
問題
無狀態Bean的作用域一般可以配置為singleton(單例模式),如果我們往singleton的Pilot類中注入prototype的Plane類,並希望每次呼叫Pilot的getPlane()方法都能返回一個新的plane Bean ,該怎麼辦呢?
如果我們使用傳統的注入方式將無法實現這樣的需求, 因為Singleton的Bean注入關聯Bean的動作僅有一次,雖然 plane Bean的作用範圍是prototype,但是 Pilot通過getPlane()方法返回的物件還是最開始注入的那個plane Bean .
如果希望每次每次呼叫getPlane()方法都返回一個新的plane Bean, 一種可選的方法是讓Pilot類實現BeanFactoryAware介面,且能夠訪問容器的引用。
但是上面的方法依賴SPring框架介面,十分不友好。 有沒有其他辦法呢?
通過方法注入的方案完美的解決這個問題。
lookup方法注入
概述
Spring IoC容器擁有複寫Bean方法的能力,主要源於CGLib類包。
CGlib可以找執行期間動態操作class位元組碼,為Bean動態建立子類或者實現類。
例項
package com.xgj.ioc.lookup;
public class Plane {
public void fly() {
System.out.println("ready to fly");
}
}
方法一 通過在配置檔案中配置的方式實現
我們宣告一個MagicPilot介面,並宣告一個getPlane()介面方法
package com.xgj.ioc.lookup;
public interface MagicPilot {
Plane getPlane();
}
下面不編寫任何實現類,僅通過配置為該介面提供動態的實現,讓getPlane介面方法每次都返回新的plane Bean。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- prototype型別的Bean -->
<bean id="plane" class="com.xgj.ioc.lookup.Plane" scope="prototype" />
<!-- 實施方法注入 -->
<bean id="magicPilot" class="com.xgj.ioc.lookup.MagicPilot">
<lookup-method name="getPlane" bean="plane" />
</bean>
</beans>
通過lookup-method元素標籤為MagicPlane的getPlane方法提供動態實現,返回prototype型別的Plane bean , 這樣Spring將在執行期為MagicPlane介面提供動態實現。 等同於方式二 。
測試類
package com.xgj.ioc.lookup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LookupTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:com/xgj/ioc/lookup/beans.xml");
Plane plane = ctx.getBean("magicPilot", MagicPilot.class).getPlane();
plane.fly();
System.out.println(ctx.isPrototype("plane"));
}
}
測試結果:
方法二 通過實現介面程式碼的方式實現
編寫MagicPilot的實現類,實現MagicPilot 和 ApplicationContextAware 介面
package com.xgj.ioc.lookup;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class MagicPilotImpl implements MagicPilot, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Plane getPlane() {
return (Plane) applicationContext.getBean("plane");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
修改配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- prototype型別的Bean -->
<bean id="plane" class="com.xgj.ioc.lookup.Plane" scope="prototype" />
<bean id="magicPilotImpl" class="com.xgj.ioc.lookup.MagicPilotImpl"/>
</beans>
修改測試類
package com.xgj.ioc.lookup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LookupTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:com/xgj/ioc/lookup/beans.xml");
MagicPilotImpl magicPilotImpl = ctx.getBean("magicPilotImpl",
MagicPilotImpl.class);
magicPilotImpl.getPlane().fly();
System.out.println(ctx.isPrototype("plane"));
}
}
執行結果
每次呼叫MagicPlane的getPlane方法都會從容器中獲取plane bean, 由於plane Bean的作用域是prototype,因此每次都能返回新的plane例項。
如果將plane Bean的作用域設定為預設的singleton ,雖然也可以潤興,但是這個時候lookup所提供的方法注入就沒有意義了。 因為我們可以很輕鬆的編寫一個magicPlane的實現類,用屬性注入的方式達到相同的目的 ,因此lookup 方法注入是有一定使用範圍的,一般在希望通過一個singleton Bean獲取一個prototype Bean時使用
小結
lookup 方法的使用場景: 一般在希望通過一個singleton Bean獲取一個prototype Bean時使用
方法替換MethodReplacer介面
概述
使用某個Bean的方法替換另外一個Bean的方法。
必須實現 org.springframework.beans.factory.support.MethodReplacer 介面,重寫reimplement方法。
例項
POJO
package com.xgj.ioc.methodReplace;
public class Plane {
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
public String getBrand() {
System.out.println("brand:" + brand);
return brand;
}
}
POJO
package com.xgj.ioc.methodReplace;
public class PilotOne {
public Plane getPlane() {
Plane plane = new Plane();
plane.setBrand("PilotOne-F22");
return plane;
}
}
POJO
package com.xgj.ioc.methodReplace;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
public class PilotTwo implements MethodReplacer {
public Object reimplement(Object obj, Method method, Object[] args)
throws Throwable {
Plane plane = new Plane();
plane.setBrand("PilotTwo-F35");
return plane;
}
}
配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用pilotTwo的MethodReplacer介面方法替換該Bean的getPlane方法 -->
<bean id="pilotOne" class="com.xgj.ioc.methodReplace.PilotOne">
<replaced-method name="getPlane" replacer="pilotTwo"/>
</bean>
<bean id="pilotTwo" class="com.xgj.ioc.methodReplace.PilotTwo"/>
</beans>
測試類
package com.xgj.ioc.methodReplace;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MethodReplacerTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:com/xgj/ioc/methodReplace/beans.xml");
ctx.getBean("pilotOne", PilotOne.class).getPlane().getBrand();
}
}
執行結果
返回了 第二個Bean的 brand:PilotTwo-F35 ,可見替換成功。
小結
用於替換他人的Bean必須實現MethodReplacer介面,Spring利用該介面的方法去替換目標Bean的方法。
總結
像lookup和methodreplacer高階功能,在實際中使用的很少,而屬性注入、建構函式注入等反而在實際專案中使用的最多,我們瞭解即可。