Bean之间的依赖

在注入属性值的时候,如果这个属性本身是一个对象怎么办呢?这就是 Bean 之间的依赖问题了。

在spring中如何处理? 引入ref标签

参考以下代码:在bean baseservice中,有 ref="basebaseservice",代表其希望此处注入的是一个 Bean 而不是一个简单的值。所以在对应的 AServiceImpl 里,也得有类型为 BaseService 的域 ref1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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!"/>
//这里的baseservice
<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>

这里aservice多出了ref字段,因此AServiceImpl 也需要拓展:

1
2
3
4
5
6
7
8
public class AServiceImpl implements com.minis.test.AService {
private String name;
private int level;
private String property1;
private String property2;
//新增
private BaseService ref1;
}

相应的,因为要解析bean中的每个属性。因此在PropertyValue中需要增加一个 isRef 字段,它可以判断某个属性是引用类型还是普通的值类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PropertyValue {
/**
* name属性:指定属性名
* value属性 : 指定属性值
* isRef属性 :是否是引用类型还是普通的值类型
*/
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; //这里新增
}

public boolean getIsRef() {
return isRef;
}

}

当他是基本类型属性时,处理流程不变;而当他是引用类型时(即isRef == true),就要增加一些处理逻辑。

解析 ref 属性,我们还是在 XmlBeanDefinitionReader 类中来处理。

简单的来说,就是程序解析 <property> 标签后,获取了 ref 的参数,根据ref是否有值,设置了 isRef 的值,把它添加到了 PropertyValues 内,最后程序调用 setDependsOn 方法,它记录了某一个 Bean 引用的其他 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
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 properties 程序解析 `<property>` 标签
List<Element> propertyElements = element.elements("property");
PropertyValues PVS = new PropertyValues();
List<String> refs = new ArrayList<>();
for (Element e : propertyElements) {
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("")) {
//如果ref有值,则说明是依赖其他bean
isRef = true;
pV = pRef;
refs.add(pRef);
}
PVS.addPropertyValue(new PropertyValue(pType, pName, pValue, isRef));
}
beanDefinition.setPropertyValues(PVS);
//end of handle properties
...
//refArray存放的是所有被依赖的bean的名称
String[] refArray = refs.toArray(new String[0]);
beanDefinition.setDependsOn(refArray);
this.simpleBeanFactory.registerBeanDefinition(beanID,beanDefinition);
}

setDependsOn 方法具体写在beanDefinition中 :

1
2
3
4
5
//   this.dependsOn是String[]类型
// String...代表了这个函数可以接受任意数量的 String 类型参数。
public void setDependsOn(String... dependsOn) {
this.dependsOn = dependsOn;
}

读取了bean的配置,并记录了其依赖关系之后,就可以来到SimpleBeanFactory中的handleProperties方法。

这里写出了处理普通属性和引用属性时的步骤。

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
 if (!isRef) {
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 {
paramValues[0] = getBean((String)pValue);
} catch (BeansException e) {
e.printStackTrace();
}
}
}

上述代码的关键之处在于 :如果是ref,则 :

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

这两个代码的意思是 :如果属性是引用(isRef 为 true),根据属性类型通过反射获取相应的类对象,并使用 getBean 方法获取对应的 Bean 对象,并将它们分别赋值给 paramTypesparamValues 数组。

完了再写一下测试文件 :

BaseService

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
package com.minis.test;


public class BaseService {
// private BaseBaseService bbs;
private String name;
private int level;
private String property1;
private String property2;
// public BaseBaseService getBbs() {
// return bbs;
// }
// public void setBbs(BaseBaseService bbs) {
// this.bbs = bbs;
// }
public BaseService() {
}
public BaseService(String name, int level) {
this.name = name;
this.level = level;
System.out.println(this.name + "," + this.level);
}
public BaseService(String name, int level,String property1,String property2) {
this.name = name;
this.level = level;
this.property1 = property1;
this.property2 = property2;

System.out.println(this.name + "," + this.level);
}
public void sayHello() {
System.out.print("Base Service says hello");
// bbs.sayHello();
}

public String getName() {
return name;
}

public int getLevel() {
return level;
}

public void setName(String name) {
this.name = name;
}

public void setLevel(int level) {
this.level = level;
}

public String getProperty1() {
return property1;
}

public String getProperty2() {
return property2;
}

public void setProperty1(String property1) {
this.property1 = property1;
}

public void setProperty2(String property2) {
this.property2 = property2;
}
}

AServiceImpl

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
package com.minis.test;


public class AServiceImpl implements com.minis.test.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);
}

public String getProperty1() {
return property1;
}

public void setProperty1(String property1) {
this.property1 = property1;
}

public String getProperty2() {
return property2;
}

public void setProperty2(String property2) {
this.property2 = property2;
}
public BaseService getRef1() {
return ref1;
}

public void setRef1(BaseService bs) {
this.ref1 = bs;
}


// public void sayHello() {
// System.out.println("a service 1 say hello");
// }
}

点击运行,出现:

`Connected to the target VM, address: ‘127.0.0.1:60772’, transport: ‘socket’
aservice创建成功 !
baseservice创建成功 !
abc,3
aservice bean created. com.minis.test.AServiceImpl : com.minis.test.AServiceImpl@402f32ff
handle properties for bean : aservice
baseservice,3
baseservice bean created. com.minis.test.BaseService : com.minis.test.BaseService@5ae9a829
handle properties for bean : baseservice
bean registerded…………. baseservice
bean registerded…………. aservice
Someone says,Hello World!
Disconnected from the target VM, address: ‘127.0.0.1:60772’, transport: ‘socket’

Process finished with exit code 0`

循环依赖

循环依赖解释

在Spring框架中,循环依赖是指两个或多个bean之间相互依赖形成的循环链。

也就是说,bean A依赖于bean B,同时bean B又依赖于bean A,它们之间形成了一个循环依赖关系。这样的情况下,当Spring容器尝试创建这些bean时,就会出现循环依赖问题

举例 :

下面这个示例中,BeanABeanB互相依赖,BeanA中持有一个BeanB对象,而BeanB中持有一个BeanA对象。在Main类的main方法中,我们尝试创建并设置了BeanABeanB的实例,并将它们相互注入。这样就形成了一个循环依赖关系。

如果我们尝试运行这段代码,会发现程序在运行时会陷入无限循环,因为无法解决循环依赖问题。这是因为每个实例在创建时都会尝试去设置对方作为依赖,而由于循环的存在,无法完成依赖注入过程。

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
public class BeanA {
private BeanB beanB; // BeanA依赖于BeanB

public BeanA() {
}

public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}

public class BeanB {
private BeanA beanA; // BeanB依赖于BeanA

public BeanB() {
}

public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}

public class Main {
public static void main(String[] args) {
BeanA beanA = new BeanA();
BeanB beanB = new BeanB();

// 循环依赖关系
beanA.setBeanB(beanB);
beanB.setBeanA(beanA);
}
}

spring中的解决方案

循环依赖问题在Spring中是一个复杂的问题,因为它涉及到对象创建和依赖注入的过程。

当Spring容器发现循环依赖时,它会尝试解决这个问题,但如果解决失败或者不加干预地允许循环依赖,就会导致应用程序出现错误或进入无限循环的状态。

为了解决循环依赖问题,Spring使用了两个阶段的处理过程:注册解析

  1. 注册阶段:在这个阶段,Spring容器会创建对象的实例,并将其添加到一个缓存中,用于跟踪已经创建的bean。当解析依赖关系时,如果发现循环依赖,Spring会使用一个半初始化的对象来解决循环依赖。这个半初始化的对象中包含了一些默认的值,但还没有完全设置好依赖关系。

    在上面的示例中,当创建BeanA实例时,发现它依赖于BeanB,但是BeanB还没有完全初始化。因此,Spring会创建一个半初始化的BeanA实例,并将其添加到缓存中。

  2. 解析阶段:在这个阶段,Spring容器会回溯到之前创建的半初始化的对象,并完成它们的依赖注入。通过这种方式,解决了循环依赖的问题。当所有的bean都完成依赖注入后,它们就可以正常使用了。

    在上面的示例中,当创建BeanB实例时,发现它依赖于BeanA。此时,Spring会回溯到之前创建的半初始化的BeanA实例,并将其设置为BeanB的依赖。然后,BeanA的依赖注入也会完成,最终解决了循环依赖的问题。

需要注意的是,Spring容器默认是支持单例bean的循环依赖解析的,因为单例bean的创建是在容器启动阶段完成的,而原型(prototype)作用域的bean不会触发循环依赖的解析。

然而,尽管Spring提供了循环依赖的解决方案,但循环依赖仍然是一种设计上的反模式。它可能导致代码的复杂性和可维护性下降,并且可能会隐藏一些潜在的问题。因此,在应用程序的设计和开发过程中,应该尽量避免出现循环依赖的情况。

解决思路

类似spring的思路 : 在 BeanFactory 中引入一个结构:earlySingletonObjects,这里面存放的就是早期的毛胚实例。创建 Bean 实例的时候,不用等到所有步骤完成,而是可以在属性还没有注入之前,就把早期的毛胚实例先保存起来,供属性注入时使用。

三级缓存

其实相应的代码在前面就写过了 :

在 getBean() 方法中,首先要判断有没有已经创建好的 bean,

​ 有的话直接取出来,

​ 如果没有就检查 earlySingletonObjects 中有没有相应的毛胚 Bean,

​ 有的话直接取出来,

​ 没有的话就去创建,并且会根据 Bean 之间的依赖关系把相关的 Bean 全部创建好。

CreateBean() 方法中调用了一个 doCreateBean(bd) 方法,专门负责创建早期的毛胚实例。

毛胚实例创建好后会放在 earlySingletonObjects 结构中,然后 createBean() 方法再调用 handleProperties() 补齐这些 property 的值。

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
@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;

}

而这个过程在spring中被称为三级缓存

三级缓存机制包括以下三个缓存,都是map:

  1. singletonObjects:用于存储完全创建好的单例bean实例。当一个单例bean创建完成后,会将其放入该缓存中。在解析循环依赖时,如果需要获取已经创建的单例bean实例,就会从这个缓存中获取。
  2. earlySingletonObjects:用于存储早期创建但未完成初始化的单例bean实例。当一个单例bean正在创建时,会将其放入该缓存中。在解析循环依赖时,如果需要获取正在创建的单例bean实例,就会从这个缓存中获取。在创建完成后,会将其移动到单例对象缓存中。
  3. singletonFactories:该缓存用于存储bean实例的工厂方法。当一个bean正在创建时,如果它依赖于其他bean,那么其他bean的工厂方法会被提前暴露到这个缓存中。这样,在解析循环依赖时,如果需要创建的bean依赖于其他正在创建的bean,就可以从这个缓存中获取其他bean的工厂方法,以解决循环依赖。

当Spring发现两个或更多个bean之间存在循环依赖关系时,它会将其中一个bean创建的过程中尚未完成的实例放入earlySingletonObjects缓存中,然后将创建该bean的工厂对象放入singletonFactories缓存中。

接着,Spring会暂停当前bean的创建过程,去创建它所依赖的bean。

当依赖的bean创建完成后,Spring会将其放入singletonObjects缓存中,并使用它来完成当前bean的创建过程。

在创建当前bean的过程中,如果发现它还依赖其他的bean,Spring会重复上述过程,直到所有bean的创建过程都完成为止。

需要注意的是,当使用构造函数注入方式时,循环依赖是无法解决的。因为在创建bean时,必须先创建它所依赖的bean实例,而构造函数注入方式需要在创建bean实例时就将依赖的bean实例传入构造函数中。如果依赖的bean实例尚未创建完成,就无法将其传入构造函数中,从而导致循环依赖无法解决。此时,可以考虑使用setter注入方式来解决循环依赖问题。

包装方法 refresh()

在 SimpleBeanFactory 中实现一个最简化的 refresh() 方法。

目的是在 Spring 体系中,Bean 是结合在一起同时创建完毕的,所以为了减少它内部的复杂性而创建。

具体的包装方法也很简单,就是对所有的 Bean 调用了一次 getBean(),利用 getBean() 方法中的 createBean() 创建 Bean 实例,就可以只用一个方法把容器中所有的 Bean 的实例创建出来了。

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