Spring基本用法6——协调作用域不同步的Bean

         前言:之前看到Spring中一个有趣的、但值得注意的问题,就是Spring中关于如何协调Bean作用域不同步?正常来讲,两个singleton作用域的bean存在依赖关系时,或者当prototype作用域的bean依赖singleton作用域的bean时,使用Spring默认提供的依赖注入管理即可,但是如果出现这种情况:singleton作用域的Bean依赖prototype作用域Bean时,会出现使用单例Bean时,每次其依赖的多例Bean都是同一个,这与多例Bean的设计初衷相违背。

本篇文章只关注以下问题:

  • 如何将多例Bean正确注入单例Bean中

1. 问题描述

        singleton作用域的Bean只有一次实例化机会,它的依赖关系也只在初始化阶段被设置,当singleton作用域的Bean依赖prototype作用域的Bean时,Spring容器会在初始化singleton作用域的Bean之前,先创建被依赖的prototype Bean,然后才初始化singleton Bean,并将prototype Bean注入到singleton Bean,这会导致以后无论何时通过singleton Bean去访问prototype Bean时,得到的永远是最初那个prototype Bean——这就相当于singleton Bean把它所依赖的prototype Bean变成了singleton行为

       由上产生出问题:如果客户端通过singleton Bean去调用prototype Bean的方法时,始终都是调用同一个prototype Bean实例,这就违背了设置prototype Bean的初衷——本来希望它具有prototype行为,但实际上它却表现出了singleton行为

2. 解决思路

  1. 放弃依赖注入:singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的;
  2. 利用Spring提供的方法注入

       第一种方式实际上是放弃Spring带来的控制反转的优势,代码主动请求新的Bean实例,必然导致程序代码与SpringAPI耦合,造成代码污染。

       方法注入通常使用lookup方法注入,使用lookup方法注入可以让Spring容器重写容器中Bean的抽象具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个not-singleton Bean。Spring通过使用JDK动态代理或cglib库修改客户端的二进制代码,从而达到上述要求。

3. Demo输出

        假设有一个Chinese类型的Bean,该Bean包含一个signName()方法,执行该方法时需要依赖于Pen的方法——而且程序希望每次执行signName()方法时都使用不同的Pen Bean,因此首先需要将Pen Bean设置为prototype作用域。

       除此之外,不能直接使用普通依赖注入将Pen Bean注入Chinese Bean中,还需要使用lookup方法注入来管理Pen Bean与Chinese Bean之间的依赖关系。其大致需要两步:

  1. 将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean;(经测试,具体类也能成功)
  2. 在<bean../>元素中添加<lookup-method../>子元素,让Spring为调用者Bean的实现类实现指定的抽象方法。

 3.1 实现被依赖Bean

          被依赖的Bean为多例模式:

package com.wj.chapter5.life.lookup;

public class Pencil implements Pen {
    
    @Override
    public String signName(String name) {
        return "我的名字是" + name;
    }
}

3.2 实现主调Bean

          主调Bean(Chinese Bean)为单例模式,其依赖于多例Bean(Pen):

package com.wj.chapter5.life.lookup;

/**
 * Chinese为单例模式,其依赖的Pen为多例
 */
public abstract class Chinese implements Person {
    private String name;
    private Pen    pen;
    
    public void setName(String name) {
        this.name = name;
    }

    // 定义抽象方法,该方法用于获取被依赖Bean
    public abstract Pen getPen();

    @Override
    public void signName() {
        pen = getPen();
        System.out.println("签名的笔型号:" + pen);
        System.out.println("我是中国人," + pen.signName(name));
    }
}

 3.3 XML配置文件

       <lookup-method../>元素需要指定两个属性:

  • name:指定需要让Spring实现的方法
  • bean:指定Spring实现该方法的返回值
<?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-4.3.xsd">
    
    <bean id="chinese" class="com.wj.chapter5.life.lookup.Chinese">
        <property name="name" value="熊燕子"></property>
        <!-- Spring只要检测到lookup-method元素,Spring会自动为该元素的name属性所指定的方法提供实现体。-->
        <lookup-method name="getPen" bean="pencil"/>
    </bean>
    <!-- 指定Pencil Bean的作用域为prototype,希望程序每次使用该Bean时用到的总是不同的实例 -->
    <bean id="pencil" class="com.wj.chapter5.life.lookup.Pencil" scope="prototype"></bean>
    
</beans>

3.4 测试代码

package com.wj.chapter5.life.lookup;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    
    // 1.指明xml配置文件位置,便于Spring读取,从而知道Bean的相关信息
    private static final String PATH_XML = "com/wj/chapter5/life/lookup/applicationContext-lookup.xml";
    
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        // 2.以类加载路径下的xml文件作为配置文件,创建Spring容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext(PATH_XML);
        
        // 3.两次获取单例Person对象
        Person p1 = ctx.getBean("chinese" , Person.class);
        Person p2 = ctx.getBean("chinese" , Person.class);
        // 4. 首先验证两次获取的单例Person是否是同一个对象
        System.out.println("两次获取的单例Person是否是同一对象:" + (p1 == p2));
        // 5. 验证依赖的多例Pencil对象是否确实是多例的
        p1.signName();
        p2.signName();
    }
}

      测试结果如下:

       可见,lookup方法注入可以正确实现singleton Bean依赖prototype Bean。

3.5  补充说明

       前面实现Person接口时,将Chinese定义为抽象类,并在里面定义抽象方法getPen()用于获取多例Pen。但这与我们习惯不符:Chinese Bean是业务上需要使用的,并不需要定为abstract抽象类,我们在开发中也确实没明确拿到抽象类Chinese的实现类(实际上Spring给我们返回的Bean就是Chinese的实现,只不过对我们透明)。

       经过测试,可以将Chinese定义为具体类,getPen()定义为空实现即可。Spring会自动帮我们实现getPen()方法的逻辑:

package com.wj.chapter5.life.lookup;

/**
 * Chinese为单例模式,其依赖的Pen为多例
 * 此处将Chinese定义为具体类,getPen为空实现
 */
public class Chinese2 implements Person {
    private String name;
    private Pen    pen;
    
    public void setName(String name) {
        this.name = name;
    }

    // 该方法用于获取被依赖Bean,此处未空实现
    public Pen getPen() {
        return null;
    }

    @Override
    public void signName() {
        pen = getPen();
        System.out.println("签名的笔型号:" + pen);
        System.out.println("我是中国人," + pen.signName(name));
    }
}

       测试结果与之前一样。

代码地址链接:http://pan.baidu.com/s/1dFJ5UQt 密码:dnjf

猜你喜欢

转载自super-wangj.iteye.com/blog/2386468