一、前言
最近在做项目时候遇到一个奇葩问题,就是 bean 依赖注入的正确性与 bean 直接注入的顺序有关系,但是正常情况下明明是和顺序没关系的啊,究竟啥情况那,不急,让我一一道来。
二、普通 Bean 循环依赖 - 与注入顺序无关
2.1 循环依赖例子与原理
public class BeanA {
- private BeanB beanB;
- public BeanB getBeanB() {
- return beanB;
- }
- public void setBeanB(BeanB beanB) {
- this.beanB = beanB;
- }
}
public class BeanB {
- private BeanA beanA;
- public BeanA getBeanA() {
- return beanA;
- }
- public void setBeanA(BeanA beanA) {
- this.beanA = beanA;
- }
}
- <property name="beanB">
- <ref bean="beanB" />
- </property>
- <property name="beanA">
- <ref bean="beanA" />
- </property>
上述循环依赖注入能够正常工作,这是因为 Spring 提供了 EarlyBeanReference 功能,首先 Spring 里面有个名字为 singletonObjects 的并发 map 用来存放所有实例化并且初始化好的 bean,singletonFactories 则用来存放需要解决循环依赖的 bean 信息(beanName, 和一个回调工厂)。当实例化 beanA 时候会触发 getBean("beanA"); 首先看 singletonObjects 中是否有 beanA 有则返回:
(1)
Object sharedInstance = getSingleton(beanName);//getSingleton(beanName,true);
if (sharedInstance != null && args == null) {
- if (logger.isDebugEnabled()) {
- if (isSingletonCurrentlyInCreation(beanName)) {
- logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
- "' that is not fully initialized yet - a consequence of a circular reference");
- }
- else {
- logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
- }
- }
- // 如果是普通bean直接返回,工厂bean则返回sharedInstance.getObject();
- bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
- protected Object getSingleton(String beanName, boolean allowEarlyReference) {
- Object singletonObject = this.singletonObjects.get(beanName);
- if (singletonObject == null) {
- synchronized(this.singletonObjects) {
- singletonObject = this.earlySingletonObjects.get(beanName);
- if (singletonObject == null && allowEarlyReference) {
- ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactories.get(beanName);
- if (singletonFactory != null) {
- singletonObject = singletonFactory.getObject();
- this.earlySingletonObjects.put(beanName, singletonObject);
- this.singletonFactories.remove(beanName);
- }
- }
- }
- }
- return (singletonObject != NULL_OBJECT ? singletonObject: null);
- }
一开始肯定没有所以会实例化 beanA,如果设置了 allowCircularReferences=true(默认为 true)并且当前 bean 为单件并且该 bean 目前在创建中,则初始化属性前把该 bean 信息放入 singletonFactories 单件 map 里面:
(2)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
- isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
- if (logger.isDebugEnabled()) {
- logger.debug("Eagerly caching bean '" + beanName +
- "' to allow for resolving potential circular references");
- }
- addSingletonFactory(beanName, new ObjectFactory() {
- public Object getObject() throws BeansException {
- return getEarlyBeanReference(beanName, mbd, bean);
- }
- });
}
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
- Assert.notNull(singletonFactory, "Singleton factory must not be null");
- synchronized(this.singletonObjects) {
- if (!this.singletonObjects.containsKey(beanName)) {
- this.singletonFactories.put(beanName, singletonFactory);
- this.earlySingletonObjects.remove(beanName);
- this.registeredSingletons.add(beanName);
- }
- }
}
然后对该实例进行属性注入 beanB,属性注入时候会 getBean("beanB"), 发现 beanB 不在 singletonObjects 中,就会实例化 beanB, 然后放入 singletonFactories,然后进行属性注入 beanA, 然后触发 getBean("beanA"); 这时候会到(1)getSingleton 返回实例化的 beanA。到此 beanB 初始化完毕添加 beanB 到 singletonObjects 然后返回,然后 beanA 初始化完毕,添加 beanA 到 singletonObjects 然后返回
2.2 允许循环依赖的开关
public class TestCircle2 {
- private final static ClassPathXmlApplicationContext moduleContext;
- private static Test test;
- static {
- moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
- moduleContext.setAllowCircularReferences(false);
- test = (Test) moduleContext.getBean("test");
- }
- public static void main(String[] args) {
- System.out.println(test.name);
- }
}
ClassPathXmlApplicationContext 类中有个属性 allowCircularReferences 用来控制是否允许循环依赖默认为 true,这里设置为 false 后发现循环依赖还是可以正常运行,翻看源码:
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {
- this(configLocations, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
- throws BeansException {
- super(parent);
- setConfigLocations(configLocations);
- if (refresh) {
- refresh();
- }
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
- throws BeansException {
- super(parent);
- setConfigLocations(configLocations);
- if (refresh) {
- refresh();
- }
}
知道默认构造 ClassPathXmlApplicationContext 时候会刷新容器。
refresh 方法会调用 refreshBeanFactory:
protected final void refreshBeanFactory() throws BeansException {
- if (hasBeanFactory()) {
- destroyBeans();
- closeBeanFactory();
- }
- try {
- // 创建bean工厂
- DefaultListableBeanFactory beanFactory = createBeanFactory();
- //定制bean工厂属性
- customizeBeanFactory(beanFactory);
- loadBeanDefinitions(beanFactory);
- synchronized (this.beanFactoryMonitor) {
- this.beanFactory = beanFactory;
- }
- }
- catch (IOException ex) {
- throw new ApplicationContextException(
- "I/O error parsing XML document for application context [" + getDisplayName() + "]", ex);
- }
}
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
- if (this.allowBeanDefinitionOverriding != null) {
- beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding.booleanValue());
- }
- if (this.allowCircularReferences != null) {
- beanFactory.setAllowCircularReferences(this.allowCircularReferences.booleanValue());
- }
}
到这里就知道了,我们在调用 moduleContext.setAllowCircularReferences(false) 前,spring 留出的设置 bean 工厂的回调 customizeBeanFactory 已经执行过了,最终原因是,调用设置前,bean 工厂已经 refresh 了,所以测试代码改为:
public class TestCircle {
- private final static ClassPathXmlApplicationContext moduleContext;
- private static Test test;
- static {
- //初始化容器上下文,但是不刷新容器
- moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"},false);
- moduleContext.setAllowCircularReferences(false);
- //刷新容器
- moduleContext.refresh();
- test = (Test) moduleContext.getBean("test");
- }
- public static void main(String[] args) {
- System.out.println(test.name);
- }
}
现在测试就会抛出异常:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference
三、工厂 Bean 与普通 Bean 循环依赖 - 与注入顺序有关
3.1 测试代码
工厂 bean
public class MyFactoryBean implements FactoryBean,InitializingBean{
- private String name;
- private Test test;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public DependentBean getDepentBean() {
- return depentBean;
- }
- public void setDepentBean(DependentBean depentBean) {
- this.depentBean = depentBean;
- }
- private DependentBean depentBean;
- public Object getObject() throws Exception {
- return test;
- }
- public Class getObjectType() {
- // TODO Auto-generated method stub
- return Test.class;
- }
- public boolean isSingleton() {
- // TODO Auto-generated method stub
- return true;
- }
- public void afterPropertiesSet() throws Exception {
- System.out.println("name:" + this.name);
- test = new Test();
- test.name = depentBean.doSomething() + this.name;
- }
}
为了简化,只写一个 public 的变量
public class Test {
- public String name;
}
public class DependentBean {
- public String doSomething(){
- return "hello:";
- }
- @Autowired
- private Test test;
}
xml 配置
- <property name="depentBean">
- <bean class="com.alibaba.test.circle.DependentBean"></bean>
- </property>
- <property name="name" value="zlx"></property>
其中工厂 Bean MyFactoryBean 作用是对 Test 类的包装,首先对 MyFactoryBean 设置属性,然后在 MyFactoryBean 的 afterPropertiesSet 方法中创建一个 Test 实例,并且设置属性,实例化 MyFactoryBean 最终会调用 getObject 方法返回创建的 Test 对象。这里 MyFactoryBean 依赖了 DepentBean,而 depentBean 本身有依赖了 Test,所以这是个循环依赖
测试:
public class TestCircle2 {
- private final static ClassPathXmlApplicationContext moduleContext;
- private static Test test;
- static {
- moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"});
- test = (Test) moduleContext.getBean("test");
- }
- public static void main(String[] args) {
- System.out.println(test.name);
- }
}
结果:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.alibaba.test.circle.DependentBean#1c701a27': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.alibaba.test.circle.Test com.alibaba.test.circle.DependentBean.test; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'test': FactoryBean which is currently in creation returned null from getObject
3.2 分析原因
当实例化 test 时候会触发 getBean("test"),会看当前 bean 是否存在
不存在则创建 Test 的实例,创建完毕后会把当前 bean 信息放入 singletonFactories 单件 map 里面
然后对该实例进行属性注入 depentBean,属性注入时候会 getBean("depentBean"),
发现 depentBean 不存在,就会实例化 depentBean, 然后放入 singletonFactories,
然后进行 autowired 注入 test, 然后触发 getBean("test"); 这时候会到(1)getSingleton 返回实例化的 test。由于 test 是工厂 bean 所以返回 test.getObject();
而 MyFactoryBean 的 afterPropertiesSet 还没被调用,所以 test.getObject()返回 null.
下面列下 Spring bean 创建的流程:
getBean()-> 创建实例 ->autowired->set 属性 ->afterPropertiesSet
也就是调用 getObject 方法早于 afterPropertiesSet 方法被调用了。
那么我们修改下 MyFactoryBean 为如下:
public Object getObject() throws Exception {
- // TODO Auto-generated method stub
- if (null == test) {
- afterPropertiesSet();
- }
- return test;
}
public void afterPropertiesSet() throws Exception {
- if (null == test) {
- System.out.println("name:" + this.name);
- test = new Test();
- test.name = depentBean.doSomething() + this.name;
- }
}
也就是 getObject 内部先判断不如 test==null 那调用下 afterPropertiesSet,然后 afterPropertiesSet 内部如果 test==null 在创建 Test 实例,看起来貌似不错,好想可以解决我们的问题。但是实际上还是不行的,因为 afterPropertiesSet 内部使用了 depentBean,而此时 depentBean=null。
3.3 思考如何解决
3.2 分析原因是先创建了 MyFactoryBean,并在在创建 MyFactoryBean 的过程中有创建了 DepentBean,而创建 DepentBean 时候需要 autowired MyFactoryBean 的实例,然后要调用 afterPropertiesSet 前调用 getObject 方法所以返回 null。
那如果先创建 DepentBean,然后在创建 MyFactoryBean 那?下面分析下过程:
首先会实例化 DepentBean,并且加入到 singletonFactories
DepentBean 实例会 autowired Test, 所以会先创建 Test 实例
创建 Test 实例,然后加入 singletonFactories
Test 实例会属性注入 DepentBean 实例,所以会 getBean("depentBean");
getBean("depentBean") 发现 singletonFactories 中已经有 depentBean 了,则返回 depentBean 对象
因为 depentBean 不是工厂 bean 所以直接返回 depentBean
Test 实例会属性注入 DepentBean 实例成功,Test 实例初始化 OK
DepentBean 实例会 autowired Test 实例 OK
按照这分析先创建 DepentBean,然后在实例化 MyFactoryBean 是可行的,修改 xml 为如下:
- <property name="depentBean">
- <ref bean="dependentBean" />
- </property>
- <property name="name" value="zlx"></property>
测试运行结果:
name:zlx
hello:zlx
果真可以了,那按照这分析,上面 XML 配置如果调整了声明顺序,肯定也是会出错的,因为 test 创建比 dependentBean 早,测试下果然如此。另外可想而知工厂 bean 循环依赖工厂 bean 时候无论声明顺序如何必然也会失败。
3.3 一个思考
上面先注入了 MyFactoryBean 中需要使用的 dependentBean,然后注入 MyFactoryBean,问题就解决了。那么如果需要在另外一个 Bean 中使用创建的 id="test" 的对象时候,这个 Bean 该如何注入那?
类似下面的方式,会成功?留给大家思考 ^^
public class UseTest {
- @Autowired
- private Test test;
}
- <property name="depentBean">
- <ref bean="dependentBean" />
- </property>
- <property name="name" value="zlx"></property>
四、 总结
普通 Bean 之间相互依赖时候 Bean 注入顺序是没有关系的,但是工厂 Bean 与普通 Bean 相互依赖时候则必须先实例化普通 bean, 这是因为工厂 Bean 的特殊性,也就是其有个 getObject 方法的缘故。
如果你也想在 IT 行业拿高薪,可以参加我们的训练营课程,选择最适合自己的课程学习,技术大牛亲授,7 个月后,进入名企拿高薪。我们的课程内容有:Java 工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty 源码分析和大数据等多个知识点。如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,你都可以来,群号为:575745314
来源: https://segmentfault.com/a/1190000012738048