文章

一次错误DI的思考

一次错误DI的思考

问题引出

在某一天的开发中,我突然发现有“神人”将controller的Bean注入到了service中,为的是调用controller中的一个方法,同时还在那个controller中注入了service,还运行的没一点毛病。按照正常的Bean加载过程,这应该造成了循环依赖,但是为什么运行没有问题呢?Spring是如何解决的呢?

依赖注入

依赖注入是一种设计模式,它将对象之间的依赖关系从代码中移除,并由容器来管理这些依赖关系。依赖注入的主要目的是降低代码耦合度。

Spring的依赖注入通过IOC容器实现。在Spring中,IOC容器负责创建和管理Bean对象,以及管理对象之间的依赖关系。

在Spring中,依赖注入通过反射机制来实现的,当IOC容器实例化Bean的时候,会检查Bean所声明的依赖关系,并尝试通过反射来注入这些依赖关系。依赖注入通常分为三种方式:构造函数注入、setter方法注入和字段注入(注解)。

那么按照问题所说的,直白来看,就相当于:

创建A对象时,解析填充A对象的属性,发现依赖的B对象未创建,则触发B对象创建;创建B对象时,发现其依赖A对象,这就产生了依赖冲突,类似于死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(new A());
    }
}

class A {
    public A() {
        new B();
    }
}

class B {
    public B() {
        new A();
    }
}

那么SpringBoot是如何解决的呢?

循环依赖场景

大部分的开发者应该都写过类似的代码:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class AServiceImpl implements AService {
    @Autowired
    private BService bService;
    ...
}
@Service
public class BServiceImpl implements BService {
    @Autowired
    private AService aService;
    ...
}

这就是经典的循环依赖场景,显然Spring在底层解决了这种问题,

那按照依赖注入的三种方式来看看每个场景

  1. 构造函数注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    @Service
    public class AServiceImpl implements AService {
    	public AServiceImpl(BServiceImpl b) {
    	}
    }
    @Service
    public class BServiceImpl implements BService {
    	public BServiceImpl(AServiceImpl a) {
    	}
    }
    

    这种注入方式会抛出异常:BeanCurrentlyInCreationException

    1
    2
    3
    4
    5
    
    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
    	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    

    构造器注入构成的循环依赖是无法解决的,这也是构造器注入最大的劣势。

    其根本原因是,Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是==已实例化==,但还==未初始化==的状态(内存已经分配,但是还未执行实例初始化)。而构造器是完成实例化时调用,因此构造器注入的循环依赖无法解决。

  2. setter方法注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    @Service
    public class AServiceImpl implements AService {
        private BService bService;
           
        @Autowired
        public void setBService(Bservice b) {
        	this.bService = b;
        }
    }
       
    @Service
    public class BServiceImpl implements BService {
        private AService aService;
           
        @Autowired
        public void setAService(Aservice a) {
        	this.aService = a;
        }
    }
    
  3. 字段注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    @Service
    public class AServiceImpl implements AService {
        @Autowired
        private BService bService;
        ...
    }
    @Service
    public class BServiceImpl implements BService {
        @Autowired
        private AService aService;
        ...
    }
    

Spring解决循环依赖的原理

Spring的循环依赖的理论依据基于Java的“引用传递”(java只有值传递),当获取对象的引用时,对象的属性是可以延后设置的。(但是构造器必须在获取引用之前,因为分配内存后,需要调用构造函数才能初始化引用)

Bean创建的流程

image-1

  • createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象。
  • populateBean:填充属性,这一步主要是对bean的依赖属性进行注入
  • initializeBean:回到一些如initMethodInitializeingBean等方法

从对单例Bean的初始化流程可以知道,循环依赖主要出现在populateBean()方法这里。

三级缓存

在Spring容器的整个声明周期中,单例Bean只有一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    // springboot3 添加的
    final Lock singletonLock = new ReentrantLock();
    // 单例Bean对象的缓存,一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    
    // 单例Bean工厂的缓存,三级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap(16);
    
    // 早期单例Bean对象的缓存(尚未填充属性),二级缓存
    private final Map<String, Consumer<Object>> singletonCallbacks = new ConcurrentHashMap(16);
    
    // 已注册的Bean缓存;单例Bean创建成功后的缓存
	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

	// 当前正在创建的Bean;表示Bean创建过程中,都会在这里缓存
	private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

获取单例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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    @Nullable
    public Object getSingleton(String beanName) {
        return this.getSingleton(beanName, true);
    }

	/**
	* 返回给定名称注册的(原始)单例对象
	* 检查已实例化的单例,并允许对当前创建的单例进行早期引用(解决循环引用)
	* beanName – 要查找的 bean 的名称 allowEarlyReference – 是否应创建早期引用
	*/
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 从一级缓存中获取Bean
        Object singletonObject = this.singletonObjects.get(beanName);
        // 如果获取不到Bean对象并且Bean对象正在创建中
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            // 从二级缓存中获取Bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 若二级缓存也无法获取,并且允许创建早期引用(三级缓存)
            if (singletonObject == null && allowEarlyReference) {
                // 获取锁
                if (!this.singletonLock.tryLock()) {
                    return null;
                }

                try {
                    // 进入同步代码块后,再次检查一、二级缓存
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            // 获取Bean工厂
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                // 将Bean从三级缓存移动至二级缓存
                                if (this.singletonFactories.remove(beanName) != null) {
                                    this.earlySingletonObjects.put(beanName, singletonObject);
                                } else {
                                    singletonObject = this.singletonObjects.get(beanName);
                                }
                            }
                        }
                    }
                } finally {
                    this.singletonLock.unlock();
                }
            }
        }

        return singletonObject;
    }

获取单例Bean时,会先从一级缓存、二级缓存中获取,若获取不到,则会利用三级缓存singletonFactories的getObject() 方法获取。

Bean对象添加至三级缓存的前提是执行了构造器,因此构造器的循环依赖无法解决。

从上面的源码可知,Spring解决循环依赖的核心是singletonFactories,缓存ObjectFactory

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
public interface ObjectFactory<T> {

	/**
	 * Return an instance (possibly shared or independent)
	 * of the object managed by this factory.
	 * @return the resulting instance
	 * @throws BeansException in case of creation errors
	 */
	T getObject() throws BeansException;

}

经过ObjectFactory.getObject()后,此时放进了二级缓存earlySingletonObjects内。这个时候对象已经实例化了,虽然并不完善,但是对象的引用已经可以被其它引用了。

上面所讲的,二级缓存存储的是尚未填充属性的单例Bean对象,那么对于一个Bean来说,什么时候该进入二级缓存,什么时候该被移除二级缓存呢?

  • 添加:向二级缓存存储Bean的地方只有getSingleton(),方法,将Bean对象从三级缓存中移动
  • 移除:addSingleton()、addSingletonFactory()、removeSingleton(),添加Bean、添加Bean工厂的时候都会删除二级缓存中对应的缓存值。

源码解析

Spring IOC 容器会将每一个正在创建的Bean 标识符缓存在singletonsInLenientCreation中,可以看作==当前创建Bean池==,Bean在创建过程中会一直缓存在里面,直到创建完毕。

这时候在按照上面所说的Bean的创建流程,首先需要getBean()

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
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    	@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

	@Override
	public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
		return doGetBean(name, requiredType, null, false);
	}

	@Override
	public Object getBean(String name, Object... args) throws BeansException {
		return doGetBean(name, null, args, false);
	}
    
    	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

        // 获取Bean的标识符
		String beanName = transformedBeanName(name);
		Object beanInstance;

		// 检查单例缓存中是否存在手动注册的单例
		Object sharedInstance = getSingleton(beanName);
        ......
            // 如果不是只检查类型,那就将Bean缓存在当前创建Bean池
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
        ......

				// Create bean instance.
				if (mbd.isSingleton()) {
                    // 这个getSingleton()是DefaultSingletonBeanRegistry中的一个重载方法
                    // 这个方法会在singletonFactory.getObject() 执行的前后,分别执行beforeSingletonCreation(beanName)、afterSingletonCreation(beanName)
                    // 保证Bean在创建过程中,放入正在创建的缓存池中
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            // 调用AbstractAutowireCapableBeanFactory的createBean
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
	}
}

在doGetBean()中会调用createBean()

1

流程总结

假设 AService 与 BService 形成了循环依赖

  1. Spring 实例化、初始化 A 这个单例Bean。AbstractBeanFactory.doGetBean("aService")

    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
    
        // 已经创建过至少一次的 bean 的名称
        private final Set<String> alreadyCreated = ConcurrentHashMap.newKeySet(256);
       
    	@SuppressWarnings("unchecked")
    	protected <T> T doGetBean(
    			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    			throws BeansException {
            String beanName = transformedBeanName(name);
    		Object beanInstance;
       
    		// 检查单例缓存中是否存在手动注册的单例
            // 此时 aService 不存在任何一级缓存中
    		Object sharedInstance = getSingleton(beanName);
            ......
            	// 在alreadyCreated中缓存Bean标识符
    			if (!typeCheckOnly) {
    				markBeanAsCreated(beanName);
    			}
            ......
    				// Create bean instance.
    				if (mbd.isSingleton()) {
                        // 通过调用createBean()来创建实例,标记aService正在创建
                        // 实例创建完成后,会移除正在创建Bean池,并且执行addSingleton()
                        // 二、三级缓存执行remove,一级缓存执行add
    					sharedInstance = getSingleton(beanName, () -> {
    						try {
    							return createBean(beanName, mbd, args);
    						}
    						catch (BeansException ex) {
    							destroySingleton(beanName);
    							throw ex;
    						}
    					});
    					beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    				}
    	}
    
  2. 执行 AbstractAutowireCapableBeanFactory.doCreateBean()

    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
    
    	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    			throws BeanCreationException {
    		// Instantiate the bean.
    		BeanWrapper instanceWrapper = null;
    		if (mbd.isSingleton()) {
    			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    		}
            // 调用构造器 / 工厂方法
    		if (instanceWrapper == null) {
    			instanceWrapper = createBeanInstance(beanName, mbd, args);
    		}
            // 此处的bean为“原始Bean”,也就是Bean实例对象 aService@1234
            Object bean = instanceWrapper.getWrappedInstance();
            ......
            // 是否要提前暴露(循序循环依赖);此处AService是允许的
    		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    				isSingletonCurrentlyInCreation(beanName));
            // 允许暴露,就把AService 绑定在ObjectFactory上,注册到三级缓存singletonFactories
            // 执行getEarlyBeanReference(),创建自动代理对象(注意执行时机 为执行三级缓存的时候)
    		if (earlySingletonExposure) {
    			if (logger.isTraceEnabled()) {
    				logger.trace("Eagerly caching bean '" + beanName +
    						"' to allow for resolving potential circular references");
    			}
    			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    		}
    		// Initialize the bean instance. exposedObject为最终返回对象
    		Object exposedObject = bean;
    		try {
                // 为 aService@1234 的属性进行赋值操作,@Autowired等注解在这里作用
                // 因此循环依赖的 bService 会在此调用 getBean("bService"),创建实例 bService@56
                // 在bService创建时,也会调用 getBean("aService"),最终调用getSingleton("..")
                // 此时上面的getEarlyBeanReference()会被执行,因此@Autowired注入的是一个代理对象
    			populateBean(beanName, mbd, instanceWrapper);
                // 实例化,这里会执行后置树立起BeanPostProcessor的两个方法
                // 
    			exposedObject = initializeBean(beanName, exposedObject, mbd);
                // 至此,相当于aService@1234已经实例化完成、初始化完成(属性也赋值了)
                ......
                   
    		}
               
        }
    

参考文章

https://cloud.tencent.com/developer/article/1497692

本文由作者按照 CC BY 4.0 进行授权