Spring的构造函数注入的循环依赖问题

一、循环依赖

  • spring的循环依赖主要是指两个类相互之间通过@Autowired自动依赖注入对方,即类A包含一个类B的对象引用并需要自动注入,类B包含一个类A的对象引用也需要自动注入。
  • 对于循环依赖问题,spring根据注入方式的不同,采取不同的处理策略,对于双方都是使用属性值注入或者setter方法注入,则spring可以自动解决循环依赖注入问题,应用程序可以成功启动;对于双方都是使用构造函数注入对方或者主bean对象(Spring在启动过程中,先加载的bean对象)使用构造函数注入,则spring无法解决循环依赖注入,程序报错无法启动。

二、可以解决的循环依赖

能够解决循环依赖的情况

  • 主bean通过属性或者setter方法注入所依赖的bean,不是通过构造函数注入。
循环依赖解决的实现
实现基础
  • 循环依赖的解决:三级缓存实现,即singletonObjects,earlySingletonObjects和singletonFactories。如下为DefaultSingletonBeanRegistry的getSingleton方法实现:该方法主要在AbstractBeanFactory的doGetBean方法调用。

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 检查一级缓存singletonObject是否存在
    	Object singletonObject = this.singletonObjects.get(beanName);
    	
    	// 当前还不存在这个单例对象,
    	// 且该对象正在创建中,即在singletonsCurrentlyInCreation列表中
    	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    		synchronized (this.singletonObjects) {
    		
    		    // 检查二级缓存earlySingletonObjects是否存在
    			singletonObject = this.earlySingletonObjects.get(beanName);
    			if (singletonObject == null && allowEarlyReference) {
    			
    			    // 检查三级缓存singletonFactory是否可以创建
    				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    				if (singletonFactory != null) {
    				
    				    // 三级缓存的对象工厂创建该对象
    					singletonObject = singletonFactory.getObject();
    					
    					// 放入二级缓存earlySingletonObjects中
    					this.earlySingletonObjects.put(beanName, singletonObject);
    					
    				    // 移除一级缓存
    					this.singletonFactories.remove(beanName);
    				}
    			}
    		}
    	}
    	return singletonObject;
    }
    
实现过程
  1. 在创建主bean对象时,AbstractBeanFactory调用DefaultSingletonBeanRegistry的getSingleton方法:将主bean放入singletonsCurrentlyInCreation列表中,从而使得以上的三级缓存实现方法getSingleton,能够进入二三级缓存earlySingletonObjects,singletonFactories查找这个主bean,主要是给依赖bean查找主bean时使用的。

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        // 省略其他代码
        
    		// 将当前beanName放入singletonsCurrentlyInCreation中,以便解决循环依赖问题
    		beforeSingletonCreation(beanName);
    		
    	try {
    	    // getObject方法会调用AbstractAutowireCapableBeanFactory的createBean方法
    		singletonObject = singletonFactory.getObject();
    		newSingleton = true;
    	}
    			
    	// 省略其他代码
    		
    }
    
    protected void beforeSingletonCreation(String beanName) {
    	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    		throw new BeanCurrentlyInCreationException(beanName);
    	}
    }
    
  2. 接着会调用AbstractAutowireCapableBeanFactory的createBean方法,而createBean方法会调用AbstractAutowireCapableBeanFactory的doCreateBean方法,doCreateBean的方法实现如下:先调用addSingletonFactory方法,在三级缓存singletonFactories中放入主bean的类型为ObjectFactory的singletonFactory对象创建工厂,这样在创建所依赖的bean对象时,可以通过三级缓存机制获取到主bean对象引用。

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    		throws BeanCreationException {
    		// 省略其他代码
    		
    		// 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过
    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    		// 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题
    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		 		
    		// 3. populateBean方法:bean对象的属性赋值
    		populateBean(beanName, mbd, instanceWrapper);
    
    		// 省略其他代码
    }
    
  3. 在doCreateBean方法中,调用populateBean进行属性赋值,其中步骤2的addSingletonFactory方法是在调用populateBean方法之前调用的,故在调用populateBean对主bean对象进行属性值注入或者setter注入时,主bean的创建工厂已经在singletonFactories缓存中了。

  4. 在populateBean方法中查找或者创建所依赖的bean对象。在创建所依赖的bean对象时,会调用到AbstractBeanFactory的doGetBean方法。而如果存在循环依赖,则所依赖的bean可以通过属性注入,setter方法注入,或者通过构造函数注入主bean:

    1. 如果通过属性值或者setter方法注入,则没有循环依赖问题。因为主bean和所依赖的bean都可以成功调用构造函数创建对象,通过对象引用来引用对方。属性值注入和setter方法注入,通过bean对象的后置处理器BeanPostProcessor来对该bean对象的属性值进行注入;
    2. 如果该依赖bean对象通过构造函数注入主bean对象,则在调用构造函数创建该依赖bean对象,需要查找主bean对象时,查找时会继续调用到AbstractBeanFactory的doGetBean方法获取主bean对象,而在AbstractBeanFactory的doGetBean方法调用getSingleton方法检查三级缓存是否可以得到主bean对象。
    3. 由步骤1和步骤2可知,由于getSingleton方法的singletonsCurrentlyInCreation和singletonFactories都已经有了主bean的相关信息,通过三级缓存可以查找到主bean对象,即通过主bean对象的创建工厂singletonFactories创建提前曝光的主bean对象,并放入二级缓存earlySingletonObjects,故可以解决为这个依赖bean对主bean的依赖注入,从而成功创建该依赖对象bean。
  5. 当创建好这个依赖bean对象之后,主bean的属性可以完整注入成功。而由于该所依赖bean包含了主bean中对象引用,故该所依赖的bean对象可以访问和使用主bean,该所依赖的bean对象也是完整的对象,从而最终解决了主bean和所依赖bean之间的循环依赖问题。

  6. 最后,注入成功之后,都会将单例bean对象放入一级缓存singletonObjects中,并移除存放提前曝光对象的二级缓存earlySingletonObjects和该bean对象的创建工厂缓存earlySingletonObjects,如下为DefaultSingletonBeanRegistry的getSingleton方法实现和addSingleton方法实现:

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    	
            // 省略其他代码
        
    		// 将当前beanName放入singletonsCurrentlyInCreation中,以便解决循环依赖问题
    		beforeSingletonCreation(beanName);
    		
        	try {
        	    // getObject方法会调用AbstractAutowireCapableBeanFactory的createBean方法
    			singletonObject = singletonFactory.getObject();
    			newSingleton = true;
    		}
    			
    		// 省略其他代码
    		
    		if (newSingleton) {
    			// 创建好bean对象后,放入singleObjects缓存中
    			addSingleton(beanName, singletonObject);
    		}
    		
    		return singletonObject;
    	}
    }
    	
    protected void addSingleton(String beanName, Object singletonObject) {
    	synchronized (this.singletonObjects) {
    	    // 放入一级缓存
    		this.singletonObjects.put(beanName, singletonObject);
    		// 移除二三级缓存
    		this.singletonFactories.remove(beanName);
    		this.earlySingletonObjects.remove(beanName);
    		this.registeredSingletons.add(beanName);
    	}
    }
    

三、无法解决的循环依赖问题

  • 在主bean中通过构造函数注入所依赖的bean。

  • 如下controller为主bean,service为所依赖的bean:

    @RestController
    public class AccountController {
        private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    
        private AccountService accountService;
    
        // 构造函数依赖注入
        // 不管是否设置为required为true,都会出现循环依赖问题
        @Autowire
        // @Autowired(required = false)
        public AccountController(AccountService accountService) {
            this.accountService = accountService;
        }
        
    }
    
    @Service
    public class AccountService {
        private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
        
        // 属性值依赖注入
        @Autowired
        private AccountController accountController;
        
    }
    

    启动打印如下:

    
    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    The dependencies of some of the beans in the application context form a cycle:
    
    ┌─────┐
    |  accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]
    ↑     ↓
    |  accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)
    └─────┘
    
  • 如果是在主bean中通过属性值或者setter方法注入所依赖的bean,而在所依赖的bean使用了构造函数注入主bean对象,这种情况则不会出现循环依赖问题。

    @RestController
    public class AccountController {
        private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
    
        // 属性值注入
        @Autowired
        private AccountService accountService;
        
    }
    
    @Service
    public class AccountService {
        private AccountController accountController;
    
        // 构造函数注入
        @Autowired
        public AccountService(AccountController accountController) {
            this.accountController = accountController;
        }
        
    }
    
    

四、总结

  • 当存在循环依赖时,主bean对象不能通过构造函数的方式注入所依赖的bean对象,而所依赖的bean对象则不受限制,即可以通过三种注入方式的任意一种注入主bean对象
  • 如果主bean对象通过构造函数方式注入所依赖的bean对象,则无论所依赖的bean对象通过何种方式注入主bean,都无法解决循环依赖问题,程序无法启动。
  • 原因主要是主bean对象通过构造函数注入所依赖bean对象时,无法创建该所依赖的bean对象,获取该所依赖bean对象的引用。因为如下代码所示。
  • 创建主bean对象,调用顺序为:
  1. 调用构造函数,2. 放到三级缓存,3. 属性赋值,其中调用构造函数时会触发所依赖的bean对象的创建。

    	    // bean对象实例创建的核心实现方法
    	    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    	    		throws BeanCreationException {
    	    		// 省略其他代码
    	    	
    	    		// 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过
    	    		instanceWrapper = createBeanInstance(beanName, mbd, args);
    	    	
    	    
    	    		// 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题
    	    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    	    	
    	    
    	    		// 3. populateBean方法:bean对象的属性赋值
    	    		populateBean(beanName, mbd, instanceWrapper);
    	    
    	    		
    	    		// 省略其他代码
    	    	return exposedObject;
    	    }
    
  • createBeanInstance是调用构造函数创建主bean对象,在里面会注入构造函数中所依赖的bean,而此时并没有执行到addSingletonFactory方法来添加主bean对象的创建工厂到三级缓存singletonFactories中。

  • 故在createBeanInstance内部,注入和创建该主bean对象时,如果在构造函数中存在对其他bean对象的依赖,并且该bean对象也存在对主bean对象的依赖,则会出现循环依赖问题,原理如下:

    主bean对象为A,A对象依赖于B对象,B对象也存在对A对象的依赖,创建A对象时,会触发B对象的创建,则B无法通过三级缓存机制获取主bean对象A的引用(即B如果通过构造函数注入A,则无法创建B对象;如果通过属性注入或者setter方法注入A,则创建B对象后,对B对象进行属性赋值,会卡在populateBean方法也无法返回)。
    故无法创建主bean对象所依赖的B,创建主bean对象A时,createBeanInstance方法无法返回,出现代码死锁,程序报循环依赖错误。

  • 16
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值