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 {
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);
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("")) { isRef = true; pV = pRef; refs.add(pRef); } PVS.addPropertyValue(new PropertyValue(pType, pName, pValue, isRef)); } beanDefinition.setPropertyValues(PVS); ... String[] refArray = refs.toArray(new String[0]); beanDefinition.setDependsOn(refArray); this.simpleBeanFactory.registerBeanDefinition(beanID,beanDefinition); }
|
setDependsOn 方法具体写在beanDefinition中 :
1 2 3 4 5
|
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 { 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
| paramTypes[0] = Class.forName(pType); paramValues[0] = getBean((String)pValue);
|
这两个代码的意思是 :如果属性是引用(isRef 为 true),根据属性类型通过反射获取相应的类对象,并使用 getBean
方法获取对应的 Bean 对象,并将它们分别赋值给 paramTypes
和 paramValues
数组。
完了再写一下测试文件 :
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 String name; private int level; private String property1; private String property2;
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");
}
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; }
}
|
点击运行,出现:
`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时,就会出现循环依赖问题。
举例 :
下面这个示例中,BeanA
和BeanB
互相依赖,BeanA
中持有一个BeanB
对象,而BeanB
中持有一个BeanA
对象。在Main
类的main
方法中,我们尝试创建并设置了BeanA
和BeanB
的实例,并将它们相互注入。这样就形成了一个循环依赖关系。
如果我们尝试运行这段代码,会发现程序在运行时会陷入无限循环,因为无法解决循环依赖问题。这是因为每个实例在创建时都会尝试去设置对方作为依赖,而由于循环的存在,无法完成依赖注入过程。
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;
public BeanA() { }
public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
public class BeanB { private BeanA 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使用了两个阶段的处理过程:注册和解析。
注册阶段:在这个阶段,Spring容器会创建对象的实例,并将其添加到一个缓存中,用于跟踪已经创建的bean。当解析依赖关系时,如果发现循环依赖,Spring会使用一个半初始化的对象来解决循环依赖。这个半初始化的对象中包含了一些默认的值,但还没有完全设置好依赖关系。
在上面的示例中,当创建BeanA
实例时,发现它依赖于BeanB
,但是BeanB还没有完全初始化。因此,Spring会创建一个半初始化的BeanA实例,并将其添加到缓存中。
解析阶段:在这个阶段,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 { Object singleton = this.getSingleton(beanName); if (singleton == null) { singleton = this.earlySingletonObjects.get(beanName); if (singleton == null) { BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); singleton = createBean(beanDefinition); this.registerSingleton(beanName, singleton); } } return singleton; } private Object createBean(BeanDefinition beanDefinition) { Class<?> clz = null; Object obj = doCreateBean(beanDefinition); this.earlySingletonObjects.put(beanDefinition.getId(), obj); try { clz = Class.forName(beanDefinition.getClassName()); } handleProperties(beanDefinition, clz, obj); return obj; }
private Object doCreateBean(BeanDefinition bd) { Class<?> clz = null; Object obj = null; Constructor<?> con = null;
try { clz = Class.forName(bd.getClassName()); 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:
- singletonObjects:用于存储完全创建好的单例bean实例。当一个单例bean创建完成后,会将其放入该缓存中。在解析循环依赖时,如果需要获取已经创建的单例bean实例,就会从这个缓存中获取。
- earlySingletonObjects:用于存储早期创建但未完成初始化的单例bean实例。当一个单例bean正在创建时,会将其放入该缓存中。在解析循环依赖时,如果需要获取正在创建的单例bean实例,就会从这个缓存中获取。在创建完成后,会将其移动到单例对象缓存中。
- 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); } } }
|