Three ways of Spring cycle dependency

Introduction: Circular dependency is a circular nested reference in N classes. If this circular dependency occurs in our daily development as a new object, the program will be called circularly at runtime until a memory overflow error occurs. Here's how Spring solves circular dependencies.

First: Constructor Parameter Cyclic Dependency

The Spring container places each Bean identifier being created in a Currently Created Bean Pool, which is maintained throughout the creation process
In this pool, so if you find yourself in the Current Creation Bean Pool during Bean Creation, you will throw it
BeanCurrentlyInCreationException exception indicates circular dependency; The beans that are created will be cleared from the Currently Created Bean Pool.

First, let's initialize three beans.

public class StudentA {
 
    private StudentB studentB ;
 
    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
 
    public StudentA() {
    }
    
    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
}
public class StudentB {
 
    private StudentC studentC ;
 
    public void setStudentC(StudentC studentC) {
        this.studentC = studentC;
    }
    
    public StudentB() {
    }
 
    public StudentB(StudentC studentC) {
        this.studentC = studentC;
    }
}
public class StudentC {
 
    private StudentA studentA ;
 
    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
 
    public StudentC() {
    }
 
    public StudentC(StudentA studentA) {
        this.studentA = studentA;
    }
}

OK, there are three basic classes above, and StudentA's parametric construct is StudentB. StudentB's parametric construction is StudentC, and StudentC's parametric construction is StudentA, which creates a circular dependency.

All three beans are managed by Spring and instantiated with reference constructs

    <bean id="a" class="com.zfx.student.StudentA">
		<constructor-arg index="0" ref="b"></constructor-arg>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB">
		<constructor-arg index="0" ref="c"></constructor-arg>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC">
		<constructor-arg index="0" ref="a"></constructor-arg>
	</bean> 

The following are the test classes:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //System.out.println(context.getBean("a", StudentA.class));
    }
}

The execution result error message is:

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?

If you understand the opening sentence, this error should not be surprising. The Spring container first creates a single StudentA, StudentA relies on StudentB, then puts A in "Currently Creating Bean Pool". At this time, it creates StudentB,StudentB relies on StudentC, and then puts B in "Currently Creating Bean Pool". At this time, it creates StudentC, and StudentC relies on StudentA, but, Student is already in the pool at this time, so errors will occur because the beans in the pool are uninitialized and therefore depend on errors. (Initialized beans are removed from the pool)

Second: setter mode singleton, default mode

If we want to say setter injection, we'd better first look at a diagram of Bean instantiation in Spring

As you can see from the first two steps in this diagram, Spring instantiates a Bean object before setting its properties

Modify profile for set injection

	<!--scope="singleton"(The default is singleton)  -->
	<bean id="a" class="com.zfx.student.StudentA" scope="singleton">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB" scope="singleton">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC" scope="singleton">
		<property name="studentA" ref="a"></property>
	</bean>

The following are the test classes:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        System.out.println(context.getBean("a", StudentA.class));
    }
}

The printout is:

com.zfx.student.StudentA@1fbfd6

Why is it correct to set it up?

In conjunction with the figure above, Spring first constructs an instantiated Bean object, where Spring puts the end of the instantiation into a Map, and Spring provides a way to get a reference to the instantiated object with this unset property. Combined with our example, when Spring instantiates StudentA, StudentB, StudentC, it immediately sets the properties of the object. StudentA relies on StudentB, it will go to Map to remove the existing single StudentB object, and so on, there will be no looping problems.

Below is the implementation in the Spring source. The following source code is in the DefaultSingletonBeanRegistry.java class in the Spring Bean package

/** Cache of singleton objects: bean name --> bean instance(Cache Map Collection of Singleton Instantiated Objects) */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
	
	/** Cache of singleton factories: bean name --> ObjectFactory(Singleton Factory Bean Cache Collection) */
	private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);
	
	/** Cache of early singleton objects: bean name --> bean instance(Earlier single object cache collections) */
	private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
	
	/** Set of registered singletons, containing the bean names in registration order(Set of instantiated object names for the singleton) */
	private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
	/**
	 * Add Singleton Instance
	 * Resolving circular references
	 * Add the given singleton factory for building the specified singleton
	 * if necessary.
	 * <p>To be called for eager registration of singletons, e.g. to be able to
	 * resolve circular references.
	 * @param beanName the name of the bean
	 * @param singletonFactory the factory for the singleton object
	 */
	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);
			}
		}
	}

The third is the setter-style prototype

Modify the configuration file to:

    <bean id="a" class="com.zfx.student.StudentA" scope="prototype">
		<property name="studentB" ref="b"></property>
	</bean>
	<bean id="b" class="com.zfx.student.StudentB" scope="prototype">
		<property name="studentC" ref="c"></property>
	</bean>
	<bean id="c" class="com.zfx.student.StudentC" scope="prototype">
		<property name="studentA" ref="a"></property>
	</bean>

Scope="prototype" means that an instance object is created for each request. The difference is that stateful beans use the Prototype scope, and stateless beans generally use the singleton singleton scope.

Test cases:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //You must get an instance of Spring administration at this point, because now scope="prototype" instantiates objects only when requested
        System.out.println(context.getBean("a", StudentA.class));
    }
}

Print results:

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?

Why is the prototype pattern wrong?

For a "prototype" scoped Bean, the Spring container cannot complete dependent injection because the Bean for the "prototype" scope is not cached and therefore cannot expose a bean in creation ahead of time.

Tags: Java Spring Back-end

Posted on Mon, 06 Dec 2021 12:50:43 -0500 by fhil85