问题
无状态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动态创建子类或者实现类。
实例
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
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高级功能,在实际中使用的很少,而属性注入、构造函数注入等反而在实际项目中使用的最多,我们了解即可。