aaaaaaa## 依赖注入:如何给Bean注入值并解决循环依赖问题? 大家好,我是郭屹。今天我们将继续我们的MiniSpring项目,进一步探讨Bean的依赖注入机制。

值的注入

在上一节课中,我们介绍了如何通过XML配置文件使用setter注入和构造器注入来定义Bean。现在我们要深入理解这些配置是如何被解析并生效的。

解析 <property><constructor-arg> 标签

为了更好地理解值的注入过程,我们首先需要了解Spring框架是如何处理<property><constructor-arg>这两个标签的。这两个标签是用于指定属性值或构造函数参数值的重要手段。 <property>标签

  • 通常用于setter注入,它允许你设置一个已经存在的bean的属性值。例如,如果你有一个名为exampleBean的bean,并且它有一个name属性,你可以使用如下配置来设置该属性的值:
1
2
3
<bean id="exampleBean" class="com.example.ExampleClass">
    <property name="name" value="Example Name"/>
</bean>

<constructor-arg>标签

  • 这个标签用于构造器注入,即在创建bean时通过构造函数传递参数。假设ExampleClass有一个接受字符串作为参数的构造函数,那么可以这样配置:
1
2
3
<bean id="exampleBean" class="com.example.ExampleClass">
    <constructor-arg value="Example Name"/>
</bean>

如何实现值的注入

  1. 解析XML配置
  • 首先,我们需要读取并解析XML配置文件,从中提取出所有的bean定义以及它们对应的属性和构造器参数信息。
  1. 实例化对象
  • 对于每个bean定义,根据其指定的方式(默认构造函数、带参构造函数等)进行实例化。
  1. 设置属性值
  • 如果bean定义中包含<property>标签,则需找到相应的setter方法并调用以设置属性值。
  1. 传递构造器参数
  • 若存在<constructor-arg>标签,则在创建bean的过程中将这些参数传递给构造函数。 接下来我们会编写代码来实现上述步骤,确保能够正确地从XML配置中加载bean定义,并完成属性值的注入与构造器参数的传递。
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <constructor-arg type="String" name="name" value="abc"/>
        <constructor-arg type="int" name="level" value="3"/>
        <property type="String" name="property1" value="Someone says"/>
        <property type="String" name="property2" value="Hello World!"/>
    </bean>
</beans>

和上面的配置属性对应,在测试类AServiceImpl中,要有相应的name、level、property1、property2字段来建立映射关系,这些实现体现在构造函数以及settter、getter等方法中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class AServiceImpl implements AService {
    private String name;
    private int level;
    private String property1;
    private String property2;

    public AServiceImpl() {
    }
    public AServiceImpl(String name, int level) {
        this.name = name;
        this.level = level;
        System.out.println(this.name + "," + this.level);
    }
    public void sayHello() {
        System.out.println(this.property1 + "," + this.property2);
    }
    // 在此省略property1和property2的setter、getter方法
}

接着,简化ArgumentValues类,移除暂时未用到的方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ArgumentValues {
    private final List<ArgumentValue> argumentValueList = new ArrayList<>();
    public ArgumentValues() {
    }
    public void addArgumentValue(ArgumentValue argumentValue) {
        this.argumentValueList.add(argumentValue);
    }
    public ArgumentValue getIndexedArgumentValue(int index) {
        ArgumentValue argumentValue = this.argumentValueList.get(index);
        return argumentValue;
    }
    public int getArgumentCount() {
        return (this.argumentValueList.size());
    }
    public boolean isEmpty() {
        return (this.argumentValueList.isEmpty());
    }
}

做完准备工作之后,我们重点来看核心工作:解析 <property><constructor-arg> 两个标签。我们要在XmlBeanDefinitionReader类中处理这两个标签。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            Element element = (Element) resource.next();
            String beanID = element.attributeValue("id");
            String beanClassName = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition(beanID,
beanClassName);
            //处理属性
            List<Element> propertyElements = element.elements("property");
            PropertyValues PVS = new PropertyValues();
            for (Element e : propertyElements) {
                String pType = e.attributeValue("type");
                String pName = e.attributeValue("name");
                String pValue = e.attributeValue("value");
                PVS.addPropertyValue(new PropertyValue(pType, pName, pValue));
            }
            beanDefinition.setPropertyValues(PVS);

            //处理构造器参数
            List<Element> constructorElements = element.elements("constructor-
arg");
            ArgumentValues AVS = new ArgumentValues();
            for (Element e : constructorElements) {
                String aType = e.attributeValue("type");
                String aName = e.attributeValue("name");
                String aValue = e.attributeValue("value");
                AVS.addArgumentValue(new ArgumentValue(aType, aName, aValue));
            }
            beanDefinition.setConstructorArgumentValues(AVS);

            this.simpleBeanFactory.registerBeanDefinition(beanID,
beanDefinition);
        }
    }
}

从上述代码可以看出,程序在加载Bean的定义时要获取 <property><constructor-arg>,只要循环处理它们对应标签的属性:type、name、value即可。随后,我们通过addPropertyValue和addArgumentValue两个方法就能将注入的配置读取进内存。 那么,将这些配置的值读取进内存之后,我们怎么把它作为Bean的属性注入进去呢?这要求我们在创建Bean的时候就要做相应的处理,给属性赋值。针对XML配置的Value值,我们要按照数据类型分别将它们解析为字符串、整型、浮点型等基本类型。在SimpleBeanFactory类中,调整核心的createBean方法,我们修改一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    private Object createBean(BeanDefinition beanDefinition) {
        Class<?> clz = null;
        Object obj = null;
        Constructor<?> con = null;
        try {
            clz = Class.forName(beanDefinition.getClassName());
            // 处理构造器参数
            ArgumentValues argumentValues =
beanDefinition.getConstructorArgumentValues();
            //如果有参数
            if (!argumentValues.isEmpty()) {
                Class<?>[] paramTypes = new Class<?>
[argumentValues.getArgumentCount()];
                Object[] paramValues = new
Object[argumentValues.getArgumentCount()];
                //对每一个参数,分数据类型分别处理
                for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
                    ArgumentValue argumentValue =
argumentValues.getIndexedArgumentValue(i);
                    if ("String".equals(argumentValue.getType()) ||
"java.lang.String".equals(argumentValue.getType())) {
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    } else if ("Integer".equals(argumentValue.getType()) ||
"java.lang.Integer".equals(argumentValue.getType())) {
                        paramTypes[i] = Integer.class;
                        paramValues[i] =
Integer.valueOf((String)argumentValue.getValue());
                    } else if ("int".equals(argumentValue.getType())) {
                        paramTypes[i] = int.class;
                        paramValues[i] = Integer.valueOf((String)
argumentValue.getValue());
                    } else { //默认为string
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    }
                }
                try {
                    //按照特定构造器创建实例
                    con = clz.getConstructor(paramTypes);
                    obj = con.newInstance(paramValues);
                }
            } else { //如果没有参数,直接创建实例
                obj = clz.newInstance();
            }
        } catch (Exception e) {
        }
        // 处理属性
        PropertyValues propertyValues = beanDefinition.getPropertyValues();
        if (!propertyValues.isEmpty()) {
            for (int i = 0; i < propertyValues.size(); i++) {
                //对每一个属性,分数据类型分别处理
                PropertyValue propertyValue =
propertyValues.getPropertyValueList().get(i);
                String pType = propertyValue.getType();
                String pName = propertyValue.getName();
                Object pValue = propertyValue.getValue();
                Class<?>[] paramTypes = new Class<?>[1];
               if ("String".equals(pType) || "java.lang.String".equals(pType))
{
                    paramTypes[0] = String.class;
                } else if ("Integer".equals(pType) ||
"java.lang.Integer".equals(pType)) {
                    paramTypes[0] = Integer.class;
                } else if ("int".equals(pType)) {
                    paramTypes[0] = int.class;
                } else { // 默认为string
                    paramTypes[0] = String.class;
                }
                Object[] paramValues = new Object[1];
                paramValues[0] = pValue;

                //按照setXxxx规范查找setter方法,调用setter方法设置属性
                String methodName = "set" + pName.substring(0, 1).toUpperCase()
+ pName.substring(1);
                Method method = null;
                try {
                    method = clz.getMethod(methodName, paramTypes);
                }
                try {
                    method.invoke(obj, paramValues);
                }
            }
        }
        return obj;
    }
}

代码处理与反射机制

1. 处理Constructor

在处理构造器时,我们首先需要从XML配置文件中提取属性值,这些值最初都是以通用的Object类型存在。根据type字段的定义,我们需要判断这些值的实际类型,目前我们支持String、Integer和int三种类型的识别。识别类型后,我们通过Java反射机制来构造对象,并将配置的属性值注入到Bean对象中,实现构造器注入。

2. 处理Property

处理属性与处理构造器类似,也需要通过type字段来确定Value的类型。不同之处在于,在确定类型后,我们需要手动构造setter方法,并利用反射机制将属性值注入到setter方法中,从而实现属性的赋值。

3. 核心机制:反射技术

无论是构造器注入还是setter注入,核心都是利用Java的反射机制来调用构造器和setter方法,并根据具体的类型将属性值作为参数传递进去。这也是所有框架实现IoC(控制反转)的基础。

4. 配置文件生效机制

通过反射机制给Bean的属性赋值,意味着配置文件中的属性设置生效了。这是实现Spring框架中Bean管理的关键步骤。

5. 依赖注入与IoC概念澄清

在实现过程中,我们经常会遇到依赖注入和IoC这两个术语。IoC是控制反转的缩写,但这个术语不够直观。通过我们的实现过程,可以更好地理解IoC的含义,即通过反射技术实现对象的创建和属性的注入,从而实现控制反转。

6. 总结

通过上述步骤,我们完成了XML配置的解析,并实现了Spring中Bean的构造器注入与setter注入方式。这不仅展示了反射技术在IoC容器中的重要性,也揭示了配置文件如何通过反射机制影响Bean的创建和属性赋值。
图片
aaaaaa在一个典型的软件开发过程中,通常是由调用者直接创建和管理所需的对象(Bean)。然而,在控制反转(Inversion of Control, IoC)模式下,这个过程是相反的:不是由开发者手动创建对象,而是由一个外部框架来负责实例化、配置以及管理这些对象。这种模式将控制权从应用程序代码转移到了IoC容器手中,因此得名“控制反转”。不过,“控制反转”这一术语有时被认为不够直观,经过一段时间的讨论后,知名程序员Martin Fowler建议使用“依赖注入”(Dependency Injection, DI)作为更贴切的描述。自此之后,“依赖注入”成为了广泛接受并使用的术语。

Bean之间的依赖问题

在实际的应用场景中,经常需要处理Bean之间的依赖关系,尤其是在属性值为另一个对象时。例如,在与MySQL数据库交互的过程中,可能会使用到Mapper类,这类映射器也是通过IoC容器启动时加载的一个Bean对象。 一种简单的想法可能是直接指定所需Bean的全限定类名及其属性名。但这种方法面临一个问题,即如何有效地表示一个复杂对象的所有属性。为此,Spring框架提供了一个优雅的解决方案——<ref>标签。该标签允许用户引用其他已定义的Bean,从而简化了对复杂对象间依赖关系的管理。

使用<ref>解决依赖

通过在Spring配置文件中利用<ref bean="beanName"/>,可以轻松地声明两个或多个Bean之间的依赖关系。这种方式不仅提高了代码的可读性,还增强了应用的灵活性与可维护性。下面是一个简化的示例,展示了如何在XML配置文件中使用<ref>来建立Bean间的关联。

1
2
3
4
<bean id="exampleService" class="com.example.ExampleServiceImpl">
    <property name="mapper" ref="exampleMapper"/>
</bean>
<bean id="exampleMapper" class="com.example.ExampleMapperImpl"/>

在此配置中,exampleService Bean依赖于exampleMapper Bean。通过ref属性指定了这一点,使得Spring能够自动处理两者之间的连接,而无需手动进行实例化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="basebaseservice" class="com.minis.test.BaseBaseService">
        <property type="com.minis.test.AServiceImpl" name="as" ref="aservice" />
    </bean>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <constructor-arg type="String" name="name" value="abc"/>
        <constructor-arg type="int" name="level" value="3"/>
        <property type="String" name="property1" value="Someone says"/>
        <property type="String" name="property2" value="Hello World!"/>
        <property type="com.minis.test.BaseService" name="ref1" ref="baseservice"/>
    </bean>
    <bean id="baseservice" class="com.minis.test.BaseService">
        <property type="com.minis.test.BaseBaseService" name="bbs" ref="basebaseservice" />
    </bean>

在上面的XML配置文件中,我们配置了一个Bean,ID命名为baseservice。随后在aservice bean的标签中设置ref=“baseservice”,也就是说我们希望此处注入的是一个Bean而不是一个简单的值。所以在对应的AServiceImpl里,也得有类型为BaseService的域ref1。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class AServiceImpl implements AService {
    private String name;
    private int level;
    private String property1;
    private String property2;
    private BaseService ref1;

    public AServiceImpl() {
    }
    public AServiceImpl(String name, int level) {
        this.name = name;
        this.level = level;
        System.out.println(this.name + "," + this.level);
    }
    public void sayHello() {
        System.out.println(this.property1 + "," + this.property2);
    }

    // 在此省略property1和property2的setter、getter方法
}

既然添加了ref属性,接下来我们很自然地会想到,要解析这个属性。下面我们就来解析一下ref,看看Spring是如何将配置的Bean注入到另外一个Bean中的。 我们为PropertyValue.java程序增加isRef字段,它可以判断属性是引用类型还是普通的值类型,我们看下修改后的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class PropertyValue {
    private final String type;
    private final String name;
    private final Object value;
    private final boolean isRef;
    public PropertyValue(String type, String name, Object value, boolean isRef)
{
        this.type = type;
        this.name = name;
        this.value = value;
        this.isRef = isRef;
}

在这里我们调整了PropertyValue的构造函数,增加了isRef参数。

接下来我们看看如何解析ref属性,我们还是在XmlBeanDefinitionReader类中来处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            Element element = (Element) resource.next();
            String beanID = element.attributeValue("id");
            String beanClassName = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition(beanID,
beanClassName);
            // handle constructor
            List<Element> constructorElements = element.elements("constructor-
arg");
            ArgumentValues AVS = new ArgumentValues();
            for (Element e : constructorElements) {
                String aType = e.attributeValue("type");
                String aName = e.attributeValue("name");
                String aValue = e.attributeValue("value");
                AVS.addArgumentValue(new ArgumentValue(aType, aName, aValue));
            }
            beanDefinition.setConstructorArgumentValues(AVS);

            // handle properties
            List<Element> propertyElements = element.elements("property");
            PropertyValues PVS = new PropertyValues();
            List<String> refs = new ArrayList<>();
            for (Element e : propertyElements) {
                String pType = e.attributeValue("type");
                String pName = e.attributeValue("name");
                String pValue = e.attributeValue("value");
                String pRef = e.attributeValue("ref");
                String pV = "";
                boolean isRef = false;
                if (pValue != null && !pValue.equals("")) {
                    isRef = false;
                    pV = pValue;
                } else if (pRef != null && !pRef.equals("")) {
                    isRef = true;
                    pV = pRef;
                    refs.add(pRef);
                }
                PVS.addPropertyValue(new PropertyValue(pType, pName, pV,
isRef));
            }
            beanDefinition.setPropertyValues(PVS);

            String[] refArray = refs.toArray(new String[0]);
            beanDefinition.setDependsOn(refArray);
            this.simpleBeanFactory.registerBeanDefinition(beanID,
beanDefinition);
        }
   }

程序解析 <property> 标签

程序首先解析 <property> 标签,获取其中的 ref 参数。这个参数指定了Bean之间的引用关系。

设置 isRef

根据获取的 ref 参数,程序有针对性地设置了 isRef 的值。这个值用于标识属性值是否是一个Bean引用。

添加到 PropertyValues

随后,程序将解析得到的属性值添加到 PropertyValues 集合中。这个集合用于存储Bean的所有属性值。

调用 setDependsOn 方法

最后,程序调用 setDependsOn 方法,这个方法记录了某一个Bean引用的其他Bean。这样,当一个Bean被创建时,它所依赖的其他Bean也会被创建。

改造 createBean() 方法

为了提高代码的可维护性和可读性,我们将 createBean() 方法进行改造,抽取出一个单独处理属性的方法。这样做可以使得代码结构更加清晰,并且方便后续的扩展和维护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
	private Object createBean(BeanDefinition bd) {
		... ...
		handleProperties(bd, clz, obj);
		return obj;
	}

	private void handleProperties(BeanDefinition bd, Class<?> clz, Object obj) {
        // 处理属性
		System.out.println("handle properties for bean : " + bd.getId());
		PropertyValues propertyValues = bd.getPropertyValues();
        //如果有属性
		if (!propertyValues.isEmpty()) {
			for (int i=0; i<propertyValues.size(); i++) {
				PropertyValue propertyValue = propertyValues.getPropertyValueList().get(i);
				String pName = propertyValue.getName();
				String pType = propertyValue.getType();
    			Object pValue = propertyValue.getValue();
    			boolean isRef = propertyValue.getIsRef();
    			Class<?>[] paramTypes = new Class<?>[1];
				Object[] paramValues =   new Object[1];
    			if (!isRef) { //如果不是ref,只是普通属性
                    //对每一个属性,分数据类型分别处理
					if ("String".equals(pType) || "java.lang.String".equals(pType)) {
						paramTypes[0] = String.class;
					}
					else if ("Integer".equals(pType) || "java.lang.Integer".equals(pType)) {
						paramTypes[0] = Integer.class;
					}
					else if ("int".equals(pType)) {
						paramTypes[0] = int.class;
					}
					else {
						paramTypes[0] = String.class;
					}

					paramValues[0] = pValue;
    			}
    			else { //is ref, create the dependent beans
    				try {
						paramTypes[0] = Class.forName(pType);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
    				try {
                        //再次调用getBean创建ref的bean实例
						paramValues[0] = getBean((String)pValue);
					}
    			}

                //按照setXxxx规范查找setter方法,调用setter方法设置属性
    			String methodName = "set" + pName.substring(0,1).toUpperCase() + pName.substring(1);
    			Method method = null;
				try {
					method = clz.getMethod(methodName, paramTypes);
				}
    			try {
					method.invoke(obj, paramValues);
				}
			}
		}
	}

这里的重点是处理ref的这几行代码。

1
2
3
//is ref, create the dependent beans
paramTypes[0] = Class.forName(pType);
paramValues[0] = getBean((String)pValue);

aaaaaa在Spring框架中,通过getBean()方法可以实现Bean之间的依赖注入。当一个Bean(例如ABean)需要另一个Bean(例如BBean)作为其属性时,Spring会在创建ABean的过程中调用getBean(BBean.class)来获取BBean的实例。如果此时BBean还未被创建,Spring会立即创建它。这样的机制使得XML配置文件中Bean的声明顺序变得无关紧要。

循环依赖问题

然而,这种设计也带来了循环依赖的问题。假设ABean依赖于BBean,而BBean又依赖于CBean,并且CBean反过来还依赖于ABean。在这种情况下,传统的依赖注入流程似乎无法正常工作,因为每个Bean都在等待其他Bean先完成初始化,从而形成一个闭环。

Spring解决循环依赖的方式

为了解决这个问题,Spring引入了earlySingletonObjects的概念。这是一种特殊的数据结构,用于存放那些已经实例化但尚未完全初始化的Bean对象。这样,在进行属性注入之前,即使某个Bean还没有完全准备好,它的早期毛胚实例也可以被其他Bean引用。 具体过程如下:

  1. BeanDefinition生成
  • 根据配置信息生成Bean定义。
  1. 加载Bean类
  • 根据Bean定义加载相应的类。
  1. 实例化
  • 创建Bean实例,这时的实例是不完整的。
  1. 保存早期毛胚实例
  • 将这个不完整的实例放入earlySingletonObjects中。
  1. 属性注入
  • 使用earlySingletonObjects中的实例进行属性注入,允许Bean之间存在循环依赖。
  1. 初始化
  • 完成所有依赖注入后,对Bean执行初始化操作。 通过这种方式,即便存在复杂的多级依赖甚至是循环依赖的情况,Spring也能有效地管理Bean的生命周期和依赖关系,确保应用能够正确运行。
    图片
    在Spring框架中,Bean的创建过程涉及到复杂的依赖关系。当一个Bean A依赖于另一个Bean B,而B又依赖于C,同时C反过来又依赖于A时,这就形成了一个循环依赖。为了处理这种情况,Spring采用了一种称为’三级缓存’的策略来管理这些早期暴露的Bean实例。

步骤解析:

  1. 实例化ABean:首先,Spring会创建一个ABean的早期不完整实例,并将其放入名为earlySingletonObjects的缓存中。这个缓存用于存放那些已经实例化但尚未完全初始化的单例对象。

  2. 注入属性到ABean:在给ABean注入属性的过程中,发现它还需要BBean。因此,Spring接着实例化BBean,同样地,将BBean的早期不完整实例也放入earlySingletonObjects缓存中。

  3. 实例化BBean:继续给BBean注入属性,此时发现它需要CBean。于是,Spring实例化CBean,并将其早期不完整实例也放入earlySingletonObjects缓存中。

  4. 实例化CBean:在给CBean注入属性时,发现它反过来还需要ABean。这时,Spring从earlySingletonObjects缓存中找到ABean的早期不完整实例,并注入到CBean中。这样,CBean就完成了创建。

  5. 完成BBean的属性注入:回到第二步,现在有了完整的CBean实例,可以将其注入到BBean中,从而完成BBean的创建。

  6. 完成ABean的属性注入:最后,回到第一步,现在有了完整的BBean实例,可以将其注入到ABean中,最终完成ABean的创建。 通过上述步骤,我们可以看到,尽管ABean、BBean和CBean之间存在复杂的循环依赖,Spring仍然能够有效地管理和解决这些问题,确保所有Bean都能被正确创建和初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@Override
public Object getBean(String beanName) throws BeansException {
    //先尝试直接从容器中获取bean实例
    Object singleton = this.getSingleton(beanName);
    if (singleton == null) {
        //如果没有实例,则尝试从毛胚实例中获取
        singleton = this.earlySingletonObjects.get(beanName);
        if (singleton == null) {
            //如果连毛胚都没有,则创建bean实例并注册
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            singleton = createBean(beanDefinition);
            this.registerSingleton(beanName, singleton);
            // 预留beanpostprocessor位置
            // step 1: postProcessBeforeInitialization
            // step 2: afterPropertiesSet
            // step 3: init-method
            // step 4: postProcessAfterInitialization
        }
    }
    return singleton;
  }

private Object createBean(BeanDefinition beanDefinition) {
    Class<?> clz = null;
    //创建毛胚bean实例
    Object obj = doCreateBean(beanDefinition);
    //存放到毛胚实例缓存中
    this.earlySingletonObjects.put(beanDefinition.getId(), obj);
    try {
        clz = Class.forName(beanDefinition.getClassName());
    }
    //处理属性
    handleProperties(beanDefinition, clz, obj);
    return obj;
}

//doCreateBean创建毛胚实例,仅仅调用构造方法,没有进行属性处理
private Object doCreateBean(BeanDefinition bd) {
		Class<?> clz = null;
		Object obj = null;
		Constructor<?> con = null;

		try {
    		clz = Class.forName(bd.getClassName());

    		//handle constructor
    		ArgumentValues argumentValues = bd.getConstructorArgumentValues();
    		if (!argumentValues.isEmpty()) {
        		Class<?>[] paramTypes = new Class<?>[argumentValues.getArgumentCount()];
        		Object[] paramValues =   new Object[argumentValues.getArgumentCount()];
    			for (int i=0; i<argumentValues.getArgumentCount(); i++) {
    				ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
    				if ("String".equals(argumentValue.getType()) || "java.lang.String".equals(argumentValue.getType())) {
    					paramTypes[i] = String.class;
        				paramValues[i] = argumentValue.getValue();
    				}
    				else if ("Integer".equals(argumentValue.getType()) || "java.lang.Integer".equals(argumentValue.getType())) {
    					paramTypes[i] = Integer.class;
        				paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
    				}
    				else if ("int".equals(argumentValue.getType())) {
    					paramTypes[i] = int.class;
        				paramValues[i] = Integer.valueOf((String) argumentValue.getValue()).intValue();
    				}
    				else {
    					paramTypes[i] = String.class;
        				paramValues[i] = argumentValue.getValue();
    				}
    			}
				try {
					con = clz.getConstructor(paramTypes);
					obj = con.newInstance(paramValues);
				}
    		}
    		else {
    			obj = clz.newInstance();
    		}
		}

		System.out.println(bd.getId() + " bean created. " + bd.getClassName() + " : " + obj.toString());
		return obj;

}

Bean的创建过程

1. createBean()方法

createBean()方法负责创建Bean实例。首先,它调用doCreateBean(bd)方法来创建早期的毛胚实例,这个毛胚实例会被存放在earlySingletonObjects结构中。随后,createBean()方法会调用handleProperties()方法来补全这些毛胚Bean的属性值。

2. getBean()方法

在getBean()方法中,首先检查是否已经存在创建好的Bean实例,如果存在,则直接返回。如果不存在,则检查earlySingletonObjects中是否有相应的毛胚Bean,如果有,则直接返回该毛胚Bean。如果两者都不存在,则会去创建Bean实例,并根据Bean之间的依赖关系,创建所有相关的Bean实例。

3. 三级缓存的误解

尽管这个过程常被称为bean的“三级缓存”,但实际上这个术语并不完全准确。这个术语源自于Spring源代码中的注释,但由于这是Spring创始人自己的注释,因此大家也沿用了这个称呼。

4. refresh()方法的作用

在Spring框架中,Bean是一起创建的。为了简化内部复杂性,Spring提供了一个重要的包装方法:refresh()。这个方法通过调用getBean()来创建Bean实例,从而一次性创建出容器中所有的Bean实例。

5. SimpleBeanFactory中的refresh()方法实现

在SimpleBeanFactory中,我们可以实现一个简化版的refresh()方法,该方法通过调用getBean()来创建所有的Bean实例。

1
2
3
4
5
6
7
public void refresh() {
    for (String beanName : beanDefinitionNames) {
        try {
            getBean(beanName);
        }
    }
}

然后我们改造ClassPathXmlApplicationContext,配合我们上一步增加的refresh()方法使用,你可以看下相应的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ClassPathXmlApplicationContext implements BeanFactory, ApplicationEventPublisher{

  SimpleBeanFactory beanFactory;
  public ClassPathXmlApplicationContext(String fileName) {
      this(fileName, true);
  }
  public ClassPathXmlApplicationContext(String fileName, boolean isRefresh) {
      Resource resource = new ClassPathXmlResource(fileName);
      SimpleBeanFactory simpleBeanFactory = new SimpleBeanFactory();
      XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(simpleBeanFactory);
      reader.loadBeanDefinitions(resource);
      this.beanFactory = simpleBeanFactory;
      if (isRefresh) {
          this.beanFactory.refresh();
      }
  }
  // 省略方法实现
 }

到这里,我们的ClassPAthXmlApplicationContext用一个refresh() 就将整个IoC容器激活了,运行起来,加载所有配置好的Bean。

你可以试着构建一下的测试代码。

1
2
3
4
public class BaseBaseService {
    private AServiceImpl as;
    // 省略 getter、setter方法
}
1
2
3
4
public class BaseService {
    private BaseBaseService bbs;
    // 省略 getter、setter方法
}
1
2
3
4
5
6
7
8
public class AServiceImpl implements AService {
    private String name;
    private int level;
    private String property1;
    private String property2;
    private BaseService ref1;
    // 省略 getter、setter方法
}

相应的XML配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="aservice" class="com.minis.test.AServiceImpl">
        <constructor-arg type="String" name="name" value="abc"/>
        <constructor-arg type="int" name="level" value="3"/>
        <property type="String" name="property1" value="Someone says"/>
        <property type="String" name="property2" value="Hello World!"/>
        <property type="com.minis.test.BaseService" name="ref1"
ref="baseservice"/>
    </bean>
    <bean id="basebaseservice" class="com.minis.test.BaseBaseService">
        <property type="com.minis.test.AServiceImpl" name="as" ref="aservice" />
    </bean>
    <bean id="baseservice" class="com.minis.test.BaseService">
        <property type="com.minis.test.BaseBaseService" name="bbs"
ref="basebaseservice" />
    </bean>

在这节课中,我们继续深化了对Spring框架的理解,特别是围绕着IoC容器的实现。在上一节课的基础上,本节课重点讲解并实现了Bean的两种注入方式:构造器注入与setter注入。

  • 构造器注入:通过构造函数将依赖关系传递给Bean实例。

  • Setter注入:利用JavaBean规范中的setter方法来设置属性值或依赖对象。

为了支持更加复杂的依赖管理,我们在属性注入时引入了ref属性的概念,它允许在一个Bean定义中引用另一个Bean。这种机制极大地提高了灵活性,使我们可以轻松地构建相互关联的对象图。

面对循环依赖这一挑战性问题,提出了“毛胚Bean”的概念,这是一种临时的、部分初始化的Bean状态,用以打破循环依赖链,从而确保整个系统能够顺利启动。

此外,还向我们的自定义IoC容器添加了一个关键性的refresh()方法。该方法整合了从创建Bean工厂到最终完成所有Bean的加载全过程,是启动Spring应用上下文的核心步骤之一。

最后,运行测试程序验证了上述功能的正确性,标志着我们自己的IoC容器已成功运作起来。

课后思考题

你认为是否可以在一个Bean的构造器中直接注入另一个Bean?对于这个问题,欢迎你在留言区分享你的看法。同时,如果这节课对你有所启发,请不要吝啬于将知识传播出去,与更多朋友一起学习成长吧!

完整源代码请访问这里