Spring day by day--day 3:lookup方法注入

场景:当我们希望通过无状态Bean获得有状态Bean时,每次获得的有状态Bean都会是同一个对象。直白点说,就是当我们希望通过一个作用域为singleton的BeanA获得一个作用域为prototype的BeanB对象的实例时,每次获得的BeanB都是同一个实例。这时我们可以考虑通过方法注入实现之。


例子:一棵苹果树AppleTree,在应用上下文中我们将其作为一个singleton的Bean,其中提供一个方法用于获得Apple,即Apple apple=AppleTree.getApple()。理所当然,我们希望每次通过AppleTree.getApple()获得的Apple都是新的对象,即Apple Bean在上下文中的作用域是prototype。代码:
/**
*苹果树,有一属性Apple,虽然这看起来很怪异,但只是用来测试,姑且将就。
*/
class AppleTree{
private Apple apple;
public Apple getApple(){
return this.apple;
}
public void setApple(Apple apple){
this.apple = apple;
}
}
/**
*苹果,有一属性“年龄”,
*通过这一属性我们可以观察每次通过AppleTree获得的Apple对象是否相同
*/
class Apple {
//苹果年龄计数器,我们设置为每次加一
public static int ageCounter = 0;
private int age;
public void setAge(int age){
this.age = age;
}
public Apple(){
this.age=++ageCounter ;
}
public String toString(){
return "apple with age "+this.age+" days";
}
}

XML文件配置:AppleTree配置为singleton,Apple配置为prototype,我们意欲每次通过AppleTree获得Apple时都获得一个新的Apple:


<bean name="apple" class="org.liaofeng.lookup.Apple" scope="prototype"/>
<bean name="appleTree" class="org.liaofeng.lookup.AppleTree" scope="singleton">
<property name="apple" ref="apple"/>
</bean>



测试代码:

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("lookup.xml"));
AppleTree tree = (AppleTree) factory.getBean("appleTree");
Apple apple1 = tree.getApple();
Apple apple2 = tree.getApple();
System.out.println(apple1);
System.out.println(apple2);


运行结果:

apple with age 1 days
apple with age 1 days


运行结果分析:很明显,两次调用tree.getApple()方法获得的Apple对象是同一个对象,我们也可以通过apple1==apple2进行证明。运行结果也很容易理解,由于AppleTree作为singleton存在,注入其Apple的动作就只有一次。即使Bean apple的作用域设置为prototype,但是根本轮不到它起作用(只调用一次,那么prototype和singleton还有区别么?),之后每次调用getApple()方法都将获得第一次注入的Apple对象。
那么我们怎样达到期望,每次通过AppleTree.getApple()都获得新的Apple呢?首先我们可以考虑修改AppleTree,让其实现BeanFactoryAware接口,然后重新修改getApple()方法,见代码:

class AppleTree implements BeanFactoryAware{
private BeanFactory factory;
public void setBeanFactory(BeanFactory factory){
this.factory = factory;
}
private Apple apple;
public Apple getApple(){
return (Apple)factory.getBean("apple");
}
public void setApple(Apple apple){
this.apple = apple;
}
}

再通过代码测试可以发现我们可以每次都获得新的Apple,但是AppleTree需要实现BeanFactoryAware接口,从而与Spring耦合了,这是Spring本身极不提倡的。有没有什么更好的实现方法呢?那就是方法注入,见改动后的代码:

interface AppleTree {
/**
* AppleTree只提供一个getApple()方法,返回Apple。
* 对于这个返回值Apple,我们可以引用一个Apple Bean来,每次返回引用的Bean,
* 将这个Apple Bean设置为prototype就可以达到每次获得不同Apple目的,
* 感觉有点像:
* <bean id="appleTree" class="...">
* <property name="apple" ref="bean"/>
* </bean>
* 不同的是,此处的AppleTree作为interface(或抽象类)存在,并没有Apple属性,
* 而是一个返回值为Apple的public方法,
* 我们要做得就是通过<lookup-method>来建立这个方法和Apple Bean之间的关系,
* 让Apple Bean作为该方法的返回值。
* @return
*/
public abstract Apple getApple();
}

再修改配置文件:

<bean name="apple" class="org.liaofeng.lookup.Apple" scope="prototype"/>
<bean name="appleTree" class="org.liaofeng.lookup.AppleTree" scope="singleton">
<lookup-method name="getApple" bean="apple" />
<!--name指定方法的名字,bean指定方法的返回值,即这个方法返回值就是bean apple,由于我们将bean apple设置成了prototype,所以每次获得到得apple对象都是不同的
-->
</bean>

再次运行测试代码,就可以发现我们每次通过AppleTree.getApple()都获得不同的Apple对象。让人很不可思议的是,AppleTree明明是一个接口,怎么可以在Bean中进行实例化呢?一般的接口和抽象类,我们都不能直接配置为Bean,因为根本不可能实例化。但是在方法注入中,Spring却恰好支持,这需要了解Spring是怎样实现以上功能的。上述功能Spring是借助cglib实现的,它可以在运行期间动态的修改Class文件,为Bean动态的创建子类或实现类。上面的例子中,运行期间Spring通过cglib创建了一个AppleTree的实现,假设为AppleTreeImpl,然后指定一属性为Apple。通过实现BeanFactoryAware(或ApplicationContextAware)每次返回新建的Apple Bean。其效果等同如以下代码:

class AppleTreeImpl implements AppleTree,BeanFactoryAware{
private BeanFactory factory;
@Override
public Apple getApple() {
return (Apple)factory.getBean("apple");
}
@Override
public void setBeanFactory(BeanFactory factory) throws BeansException {
this.factory = factory;
}
}

看到这里,明白了吧?最后需要注意的是,lookup方法注入需要AppleTree为接口和抽象类,其getApple()抽象方法要是public或protected(废话!)~~~~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值