[Spring] Spring's 3 ways to solve circular dependencies

Insert picture description here

Original: http://www.javastack.cn/article/2020/spring-cycle-depends-on-three-ways/

Circular dependency is a circular nested reference in N classes. If this circular dependency occurs in the way of new objects in daily development, the program will be called repeatedly at runtime until the memory overflows and an error is reported.

Let me talk about how Spring solves circular dependencies.

The first type: cyclic dependence of constructor parameters

The Spring container will put each Bean identifier being created in a "currently created Bean pool", and the Bean identifier will remain in this pool during the creation process.

Therefore, if you find that you are already in the "currently created Bean pool" during the process of creating a Bean, a BeanCurrentlyInCreationException will be thrown to indicate a circular dependency; and the created Bean will be removed from the "Currently Created Bean Pool".

First, we 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, the above are three very basic classes, and the parameter structure of StudentA is StudentB. The parameterized structure of StudentB is StudentC, and the parameterized structure of StudentC is StudentA, which creates a circular dependency.

We all give these three Beans to Spring for management and instantiate them with parameterized construction.

<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 is the test class:

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 singleton StudentA, and StudentA depends on StudentB, and then places A in the "currently created Bean pool".

StudentB is created at this time, StudentB depends on StudentC, and then B is placed in the "currently created Bean pool", and StudentC is created at this time, and StudentC depends on StudentA.

However, at this time, the Student is already in the pool, so an error will be reported, because the beans in the pool are not initialized, so they will rely on the error, and the initialized Bean will be removed from the pool.

The second type: setter mode singleton, default mode

If you want to talk about setter injection, we'd better look at a picture of Bean instantiation in Spring

Insert picture description here

As shown in the first two steps in the figure: Spring first instantiates the Bean object and then sets the object properties. Why does the bean in Spring default to a singleton? I suggest you take a look at this article.

Pay attention to the WeChat public account: Java technology stack, and reply in the background: spring, you can get the N latest Spring tutorials I have compiled, all of which are dry goods.

Modify the configuration file for set injection

<!--scope="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 is the test class:

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 print result is:

com.zfx.student.StudentA@1fbfd6

Why is there no error when using the set method?

Based on the above picture, Spring first instantiates the Bean object with construction. At this time, Spring will put the object that has been instantiated into a Map, and Spring provides a method to get the reference of this instantiated object with no attributes set. .

Based on our example, when Spring instantiates StudentA, StudentB, and StudentC, it will set the properties of the object immediately. At this time, StudentA depends on StudentB, and will go to the Map to take out the singleton StudentB object that exists in it. By analogy, there will be no loop problem.

The following is the implementation method in the Spring source code. The following source code is in the DefaultSingletonBeanRegistry.java class in Spring's Bean package

/** Cache of singleton objects: bean name --> bean instance(缓存单例实例化对象的Map集合) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

/** Cache of singleton factories: bean name --> ObjectFactory(单例的工厂Bean缓存集合) */
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);

/** Cache of early singleton objects: bean name --> bean instance(早期的单身对象缓存集合) */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** Set of registered singletons, containing the bean names in registration order(单例的实例化对象名称集合) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/**
 * 添加单例实例
 * 解决循环引用的问题
 * 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 type: setter mode prototype, prototype

Modify the configuration file as:

<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 every request will create an instance object.

The difference between the two is: Stateful beans use Prototype scope, and stateless beans generally use singleton scope.

Test case:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
        //此时必须要获取Spring管理的实例,因为现在scope="prototype" 只有请求获取的时候才会实例化对象
        System.out.println(context.getBean("a", StudentA.class));
    }
}

Print result:

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 did the prototype mode report an error?
For beans in the "prototype" scope, the Spring container cannot complete dependency injection, because the beans in the "prototype" scope are not cached by the Spring container, so a created bean cannot be exposed in advance.

Guess you like

Origin blog.csdn.net/qq_21383435/article/details/108491324
Recommended