Overview of cyclic dependency issues

What is a cyclic dependency problem?

Class dependencies form a closed loop, which leads to the problem of circular dependencies.

For example, in the following figure, class A depends on class B, class B depends on class C, and finally class C depends on class A, thus forming the circular dependency problem.

Case study of cyclic dependency problem

  • Demo code:
public class ClassA {
	private ClassB classB;

	public ClassB getClassB(a) {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB; }}public class ClassB {
	private ClassA classA;

	public ClassA getClassA(a) {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA; }}Copy the code
  • Configuration file:

      
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="classA" class="ioc.cd.ClassA">
		<property name="classB" ref="classB"></property>
	</bean>
	<bean id="classB" class="ioc.cd.ClassB">
		<property name="classA" ref="classA"></property>
	</bean>
</beans>
Copy the code
  • Test code:
	@Test
	public void test(a) throws Exception {
		// Create the IoC container and initialize it
		String resource = "spring/spring-ioc-circular-dependency.xml";
		ApplicationContext context = new ClassPathXmlApplicationContext(resource);
		// Get an instance of ClassA (where cyclic dependencies occur)
		ClassA classA = (ClassA) context.getBean(ClassA.class);
	}
Copy the code
  • Analyze loop dependencies through the source code of the Spring IOC process:

How many cyclic dependencies are there in the above case?

There are three main cases of cyclic dependencies in Spring:

  • Circular dependency issues that arise when dependency injection is done through constructors.
  • Dependency injection via setter methods is a circular dependency problem that arises in the multi-case (prototype) pattern.
  • Dependency injection via setter methods is a circular dependency problem in singleton mode.

Note: In Spring, only the [third] loop-dependency problem is resolved; the other two methods both raise exceptions when they encounter a loop-dependency problem.

It’s easy to explain:

  • In the case of the first constructor injection, the new object is blocked, which is a chicken-and-egg historical conundrum.
  • In the case of the second setter method &&, each time getBean() is created, a new Bean is created, and so on, an endless number of beans are created, which eventually leads to OOM problems.

How do I solve circular dependencies?

So how does Spring solve the problem of circular dependencies caused by dependency injection of setter methods? Take a look at the following figure (which is mainly solved by two caches) :

Introduction to Spring’s three caches

Spring has three caches for storing singleton Bean instances. These caches are mutually exclusive and are not stored simultaneously for instances of the same Bean.

If getBean is called, the specified Bean instances need to be fetched from each of the three caches. The read order is level 1 cache -> Level 2 cache -> Level 3 cache

Level 1 cache: Map<String, Object> singletonObjects

What does level 1 cache do?

  • Used to store Bean instances (already created) created in singleton mode.
  • This cache is intended for external use by programmers using the Spring framework.

What data is stored?

  • K: Name of the bean
  • V: Instance object of bean (if there is a proxy object, it is already created)

Level 2 cache: Map<String, Object> earlySingletonObjects

What does level 2 cache do?

  • Used to store instances of beans created in the singleton pattern (references to beans that were exposed in advance, while the Bean is still being created).
  • This cache is for internal use, meaning that the Spring framework’s internal logic uses it.
  • To solve the problem of how to replace the first classA reference with a proxy object (if any), please refer to the demo case

What data is stored?

  • K: Name of the bean
  • V: Instance object of the bean (proxy object if there is a proxy object, the bean is still being created)

Map<String, ObjectFactory<? >> singletonFactories

What does level 3 cache do?

  • The ObjectFactory object is used to store references (being created) to Bean instances that are exposed in singleton mode.
  • This cache is for internal use, meaning that the Spring framework’s internal logic uses it.
  • This cache is the biggest contributor to solving loop dependencies

What data is stored?

  • K: Name of the bean
  • V: ObjectFactory, which holds a reference to the previously exposed bean

Why use ObjectFactory for the third level cache? Proxy objects need to be generated in advance.

When is the reference to the Bean pre-exposed to the ObjectFactory holding of the third-level cache? The timing is after the first step of instantiation and before the second step of dependency injection.

conclusion

That’s the key to Spring’s solution to loop dependencies! To sum up, it is necessary to make clear the following points:

  • Figure out what Spring level 3 caching is for?
  • Figure out what the ObjectFactory does in the third level cache?
  • Figure out why you need a second level cache?
  • Figure out when to use level 3 caching (add and query operations)?
  • Figure out when to use level 2 caching (add and query operations)?
  • Who exactly is stored in the Spring container (level 1 cache) when the target object generates a proxy object?