欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring基本用法6——协调作用域不同步的Bean 博客分类: Spring Spring作用域不同步具体类实现lookup方法 

程序员文章站 2024-02-05 18:02:58
...

         前言:之前看到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();
    }
}

      测试结果如下:

Spring基本用法6——协调作用域不同步的Bean
            
    
    博客分类: Spring Spring作用域不同步具体类实现lookup方法        可见,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

  • Spring基本用法6——协调作用域不同步的Bean
            
    
    博客分类: Spring Spring作用域不同步具体类实现lookup方法 
  • 大小: 35 KB