java注入bean依赖,论Spring中循环依赖的正确性与Bean注入的顺序关系

一、前言

最近在做项目时候遇到一个奇葩问题,就是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;

}

}

上述循环依赖注入能够正常工作,这是因为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配置

其中工厂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为如下:

测试运行结果:

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;

}

四、 总结

普通Bean之间相互依赖时候Bean注入顺序是没有关系的,但是工厂Bean与普通Bean相互依赖时候则必须先实例化普通bean,这是因为工厂Bean的特殊性,也就是其有个getObject方法的缘故。

d0c1501a6d8bb921cf36400dc89de69f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值