“This is the 22nd day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

In fact, Spring is now the basic framework we can not do without Java program development. In my opinion, besides JDK, Spring is the Java middleware we use most. Today, we will learn about Spring’s cyclic dependency.

Q&A

  • Can you explain the three-level caching in Spring?
  • What are the three levels of caches? What are the similarities and differences between the three maps?
  • What are circular dependencies? Please talk about? Have you read the source code for Spring? Normally we’re talking about what is a Spring container?
  • Why can’t circular dependencies be solved in multiple cases?

What are circular dependencies?

Multiple beans depend on each other to form a closed loop. For example: A depends on B, B depends on C, C depends on A sample code

public class T1 {

  class A {
    B b;
  }
  class B {
    C c;
  }
  class C { A a; }}Copy the code

For example, A depends on B, B depends on C, and C depends on A

In general, if you ask the Spring container how loop dependencies are handled internally, you are referring to the default singleton Bean, where properties reference each other.

@Component
public class A {
    @Autowired
	private B b;
}

@Component
public class B {
    @Autowired
	private C c;
}
 
@Component
public class C {
    @Autowired
	private A a;
}
Copy the code

Circular dependency specification

The official instructions

Refer to the official website, which will be linked below

Official website link:Docs. Spring. IO/spring – fram…

conclusion

Constructor injection: Circular dependencies are not supported. Conclusion: We are AB cycle dependent problem. As long as A’s mode is setter and the Singleton does not have A cyclic dependency problem.

Abnormal BeanCurrentlyInCreationException circular dependencies

Circular dependencies Inject dependent objects into the Spring container in two ways

Method one: Constructor injection dependency

@Component
public class ServiceA{

    private ServiceB serviceB;
        
    public ServiceA(ServiceB serverB) {
       this.serivceB = serviceB; }}@Component
public class ServiceB{

    private ServiceA serviceA;
        
    public ServiceB(ServiceA serviceA) {
       this.serviceA = serviceA; }}Copy the code

Constructor loop dependencies are unsolvable, and you can’t make constructor injection support loop dependencies.

Method 2: Inject dependencies in SET mode

@Component
public class ServiceA{

    private ServiceB serviceB;
        
    public setServiceB(ServiceB serverB) {
       this.serivceB = serviceB; }}@Component
public class ServiceB{

    private ServiceA serviceA;
        
    public setServiceB(ServiceA serviceA) {
       this.serviceA = serviceA; }}Copy the code

Case Demonstration (Loop dependencies based on the Spring container)

Ordinary Java base code class A, class B

@Data
public class A{
  private B b;
  
  public A(a) {
     System.out.println("----- A create success"); }}@Data
public class B{
  private a a;

  public B(a) {
     System.out.println("------ B create success"); }}Copy the code

Cyclic dependency resolution

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);
Copy the code

Loop dependencies based on the Spring container

  • The default singleton scenario supports circular dependencies and does not report errors
  • Prototype scenarios do not support loop dependencies and will report errors

Code demo

  • Loop dependent code
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A {

    @Autowired
    private B b;
}

@Component
public class B {

    @Autowired
    private A a;
}

Copy the code
  • Default singleton, modified to prototype
// Add a comment
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Copy the code
  • A test program
class BTest {


    @Configuration
    @Import({A.class, B.class})
    public static class TestConfig {}@Test
    public void currentlyincreation(a) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(TestConfig.class);
        // If the bean is not loaded lazily, it is not loaded lazilyA a = applicationContext.getBean(A.class); System.out.println(a); }}Copy the code
  • Cyclic dependent exception

Spring internally solves the problem of loop dependency through tertiary caching

Core classes: DefaultSingletonBeanRegistry

Level 1 cache: (also called singleton pool) singletonObjects: Holds Bean objects that have gone through a full life cycle

Level 2 cache: earlySingletonObjects: Stores Bean objects that have been exposed earlier than the end of the Bean’s life cycle (properties have not yet been populated)

Map

> singletonFactories can hold Bean factories.
,>

Only singleton beans are exposed ahead of time through the level 3 cache to solve the loop dependency problem, while non-singleton beans are newly created each time they are fetched from the container, so non-singleton beans are not cached and will not be placed in the level 3 cache.

Instantiate/initialize the definition

1. Instantiation: Apply for a piece of memory space in memory. (Ex: Rent a nice house, own furniture sofa, bed not moved in yet.)

2. Initialize attribute filling: complete the assignment of various attributes; (such as: decoration, home appliances and furniture)

Four methods of three maps, overall related objects Level 1: singletonObjects: Hold beans that have been initialized.

Layer 2: earlySingletonObjects: Holds instantiated, but uninitialized beans.

Map

> singletonFactories stores FactroyBeans. If class A implements A FactoryBean, then the dependency injection is not class A, but class A’s FAC day FactoryBean.
,>

/** * Cache of singleton objects: bean names -- bean instances, i.e. : all singleton pools * represent bean objects that have undergone a full life cycle *  first level cache  */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** * Singleton factory cache: bean name --ObjectFacotry * holds the generated bean factory *  third level cache  */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);

/** * Caching of early singletons: The bean name -- bean instance * indicates that the bean has not completed its life cycle (the bean's properties have not been filled) and is stored in this cache * that is, the instantiated bean is placed in this cache *  the second level cache  */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Copy the code

1, create A need for B, so A put itself in the level 3 cache, to instantiate B

2, when B instantiates, it finds that A is needed, so B first checks the level-1 cache, then checks the level-2 cache, or does not check the level-3 cache, finds A and then puts the A in the level-2 cache, and deletes the A in the level-3 cache.

3. After the successful initialization, B will put itself into the level-1 cache (at this time, A in B is still in the state of creation) and then create A. At this time, B has been created, directly get B from the level-1 cache, and then complete the creation, and put A into the level-1 cache

The ObjectFactory interface

@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(a) throws BeansException;

}

Copy the code

DEBUG Breakpoint debugging

1. Bean creation2. Put it into the three-level cache

3. Property fill (i.e. do property injection)

Spring loop dependencies resolve process combing

Sorting process

1. Call the doGetBean () method to retrieve the beanA, so call getSingletion() to retrieve the beanA from the cache

In the getSingletion() method, null is returned

GetSIngletion () = null; doGetBean() = getSIngletion() = null;

4. In the getSingletion() method, beanB_name is added to a collection to indicate that the bean is being created, and the createBean method of the anonymous inner class is called back

5, enter AbstractAutowireCapableBenaFactory# doCreateBean, first reflection calls the constructor to create beanA instance, and then judge, Whether it is a singleton, whether references are allowed to be exposed in advance (generally true for singleton cases), whether it is being created (that is, in the collection of step 4). If true, beanA will be added to level 3 cache.

6. Fill the attributes of beanA. At this time, it is detected that beanA depends on beanB, so it starts to search for beanB

7. Call the doGetBean method, as in the beanA procedure above, to find the beanB in the cache, create it if there is none, and then populate the beanB with properties.

8. Call getSingleton() to get beanA from level 1, Level 2, and level 3 caches successively. At this time, the level 3 caches get beanA creation factory. Get the singletonObject by creating a factory that points to the beanA instantiated by the doCreateBean() method above.

9. BeanB then obtains beanA’s dependencies, and beanB completes the instance and moves beanA from level 3 to Level 2.

10. BeanA then proceeds with its property population, gets the beanB, and completes its creation. The callback to getSingleton() continues to move beanA from level 2 cache to level 1 cache.

The Spring loop relies on summaries

Spring creates beans in two main steps, creating the original Bean object, then populating the properties and initializing.

Each time we create a Bean, we check the cache for the Bean, because it is a singleton and there can only be one

After we create beanA’s original object and put it in the level 3 cache, it’s time to fill in the properties, and when we find that we depend on beanB, we create beanB directly, same process, After creating beanB to co-ordinate the domestic attributes, it is found that it depends on beanA and again the same process.

The difference is

At this time, the level 3 cache can only query the original object beanA, so there is no need to continue to create, use it to inject beanB, complete the creation of beanB

Now that the beanB is created, beanA can bend the property fill step and perform the rest of the logic to complete the loop

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Get the instance from singletonObject, which is a prepared bean instance
    Object singletonObject = this.singletonObjects.get(beanName);
    / / isSingletonCurrentlyInCreation () to judge the current singleton bean is created
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
	        		// If there is none in level 1 cache, go to level 2 cache
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // The level 2 cache does not have any dataObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
                        if(singletonFactory ! =null) {
                            // If the level 3 cache exists, move it to level 2 cache
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}
Copy the code

Spring’s solution to loop dependencies is the concept of an “intermediate state” of beans. The intermediate state refers to the state that has been instantiated but has not been initialized. The instantiation process is created through the constructor. If A has not been created, how can it be exposed in advance, so the constructor’s cyclic dependency cannot be solved.

Spring uses level 3 caching to solve the problem of loop dependency for singletons.

The primary cache is singletonObjects, the secondary cache is earlySingletonObjects, and the tertiary cache is singletonFactories.

Assume A, B A circular reference, instantiation put it in when I was A three-level cache, then filling properties, found that rely on B, the same process are instantiated in the three levels of cache, and then to fill properties found themselves rely on A, this time from the cache lookup to get up early exposed A, no AOP agent, Directly inject A original object into B, complete the initialization of B, for property filling and initialization, this time AFTER B is completed, to complete the remaining A steps, if there is an AOP proxy, will be AOP processing to obtain the proxy after A, injection B, go to the rest of the process.