Spring Basic Usage 6 - Reconciling Beans with Unsynchronized Scopes

         Foreword: I saw an interesting but noteworthy question in Spring before, that is, how to coordinate Bean scope synchronization in Spring? Normally, when two beans in the singleton scope have dependencies, or when the beans in the prototype scope depend on the beans in the singleton scope, you can use the dependency injection management provided by Spring by default, but if this happens: singleton When a scoped bean depends on a prototype-scoped bean, when a singleton bean is used, the multiple instances of beans it depends on are the same each time, which is contrary to the original intention of the design of multiple instanced beans.

This article only focuses on the following issues:

  • How to correctly inject multiple instance beans into singleton beans

1. Problem description

        A singleton-scoped bean has only one opportunity to instantiate, and its dependencies are only set during the initialization phase. When a singleton-scoped bean depends on a prototype-scoped bean, the Spring container will first initialize the singleton-scoped bean before initializing the singleton-scoped bean. Create the dependent prototype bean, then initialize the singleton bean, and inject the prototype bean into the singleton bean, which will cause that whenever you access the prototype bean through the singleton bean in the future, you will always get the original prototype bean - this is It is equivalent to a singleton bean turning the prototype bean it depends on into a singleton behavior .

       The problem arises from the above: if the client calls the method of the prototype bean through the singleton bean, it always calls the same instance of the prototype bean, which violates the original intention of setting the prototype bean . On top it exhibits singleton behavior .

2. Solutions

  1. Abandoning dependency injection: every time a bean in the singleton scope needs a bean in the prototype scope, it actively requests a new bean instance from the container to ensure that the injected prototype bean instance is the latest;
  2. Use the method injection provided by Spring

       The first way is actually to give up the advantages of inversion of control brought by Spring. The code actively requests a new Bean instance, which will inevitably lead to the coupling of the program code with the Spring API, resulting in code pollution.

       Method injection usually uses lookup method injection. Using lookup method injection allows the Spring container to rewrite the abstract or concrete methods of beans in the container and return the results of searching other beans in the container. The searched bean is usually a not-singleton bean. Spring achieves the above requirements by modifying the client's binary code using the JDK dynamic proxy or the cglib library.

3. Demo output

        Suppose there is a Chinese-type Bean, which contains a signName() method. When executing this method, it needs to rely on the Pen method - and the program wants to use a different Pen Bean every time the signName() method is executed. Therefore, it is necessary to first Set the Pen Bean to prototype scope.

       In addition, the Pen Bean cannot be injected into the Chinese Bean directly by ordinary dependency injection, and the lookup method injection is also required to manage the dependency between the Pen Bean and the Chinese Bean. It roughly requires two steps:

  1. Define the implementation class of the caller Bean as an abstract class, and define an abstract method to obtain the dependent Bean; (after testing, the concrete class can also succeed)
  2. Add the <lookup-method../> child element to the <bean../> element to let Spring implement the specified abstract method for the implementation class of the caller bean.

 3.1 Implementing Dependent Beans

         Dependent beans are in multi-instance mode:

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326269608&siteId=291194637