What are circular dependencies

Cyclic dependencies are nested references between N classes. For example, A depends on B, and B depends on A. When A is instantiated, it needs the B attribute, so it needs the A attribute… If Spring does not handle this cyclic dependency, the handler will execute indefinitely, causing memory to run out and the system to crash.

Loop dependencies are divided into constructor loop dependencies and property loop dependencies. Since Spring does not support constructor loop dependencies, an error will be reported directly, so we will only discuss property loop dependencies.

Bean instantiation steps

Bean instantiation can be roughly divided into three steps

                                           

Cyclic dependencies occur in the instantiation and injection of properties steps.

The solution

When A is instantiated, the incomplete object A is cached, so that when B is instantiated and injected into A, the object A can be obtained from the container to complete initialization. Finally, the B object is injected into A, which completes the initialization.

Spring introduces a three-level cache to address the problem of loop dependencies

/** Level 1 cache, caches completed initialization bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
/** Level 2 cache, which caches the raw bean(not yet populated with attributes) to resolve loop dependencies */
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
/** Level 3 caches bean factory objects to resolve loop dependencies */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap(16);Copy the code

When a singleton bean is fetched, it accesses level 1 cache, level 2 cache, and level 3 cache in sequence, and returns on a cache hit.

The source code parsing

Sequence diagram



GetBean is the entry method to get beans from the container, which in turn calls the doGetBean method. Look at this method.

doGetBean

protected  T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {// Try to get the target bean Object using the bean name, e.g. Object sharedInstance = getSingleton(beanName); // Our target objects here are singletonsif(mbd.issingleton ()) {// Here we try to create the target object. The second argument passes an object of type ObjectFactory, which is written using Java8 lamada // expression. As long as the getSingleton() method above returns nothing, SharedInstance = getSingleton(beanName, () -> {try {// Try to create the target objectreturncreateBean(beanName, mbd, args); } catch (BeansException ex) { throw ex; }}); }return (T) bean;
}Copy the code

There are two methods in this method called getSingleton. The first getSingleton looks up the bean from the cache, and if the cache misses, the second getSingleton tries to create the target object and inject the dependency.

When the first call to doGetBean gets the A object, the first getSingleton returns null, and the second getSingleton creates the A object and injects the B object. Call doGetBean to get B, the first getSingleton returns null, the second getSingleton creates the B object, retrieves and injects the original A object, and the B object is initialized. Finally, the B object is injected into A, which completes the initialization.

Take a look at the source code for the first getSingleton

getSingleton(String beanName, boolean allowEarlyReference)

@nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) { Directly returns Object singletonObject = this. SingletonObjects. Get (beanName); // If the target object does not exist in the cache, the current object is not in the process of creation. In the previous section, after the first attempt to obtain an instance of object A, object A is marked as being created, so the last attempt to obtain object A is marked as being createdifThe judgment will betrue
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if(singletonObject == null && allowEarlyReference) {// The singletonFactories (singletonObject == null && allowEarlyReference) {// The singletonFactories (singletonObject == null && allowEarlyReference) The value is A // object of type ObjectFactory. Here, for A and B, calling getObject() on the graph returns instances of objects A and B, No matter whether the semi-finished ObjectFactory singletonFactory = this. SingletonFactories. Get (beanName);if(singletonFactory ! = null) {/ / access to the target object instance singletonObject = singletonFactory. GetObject (); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); }}}}return singletonObject;
}Copy the code

The target object is first fetched from the primary cache singletonObjects, or the bean is fetched from the secondary cache earlySingletonObjects if it does not exist and the target object is marked as being created. If it doesn’t, go ahead and visit the level-three singletonFactories cache to get the bean factory object and get the target object from the factory object. Puts the target object into the level 2 cache and deletes the level 3 cache.

The second getSingleton method

getSingleton(String beanName, ObjectFactory
singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<? > singletonFactory) { synchronized (this.singletonObjects) { // ...... / / call getObject method to create the bean instance singletonObject = singletonFactory. GetObject (); newSingleton =true;

        ifAddSingleton (beanName, singletonObject) {// Add the bean to the singletonObjects cache and remove bean-related records from other collections; } / /... / / return singletonObjectreturn (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}Copy the code

The main logic of this method is to create the target object by calling the getObject() method of the singletonFactory, and then putting the bean into the cache.

Looking at the implementation of the getObject method, since the second argument to getSingleton is passed as an anonymous inner class in a doGetBean call, you are actually calling the createBean method, which in turn calls the doCreateBean.

doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; / /... InstanceWrapper = createBeanInstance(beanName, MBD, args); // Get the bean Object from the BeanWrapper Object, where the bean points to an original Object. Final Object bean = (instanceWrapper! = null ? instanceWrapper.getWrappedInstance() : null); /* * earlySingletonExposure is used to indicate whether a reference to the original object is "pre-exposed", used to resolve loop dependencies. * For singleton beans, this variable is typicallytrue. A more detailed explanation can refer to my previous articles * / Boolean earlySingletonExposure = (MBD) isSingleton () && enclosing allowCircularReferences && isSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure) {// add the bean factory object to the singletonFactories (beanName, New ObjectFactory<Object>() {@override public Object getObject() throws BeansException {/* * Get an early reference to the original Object, In the getEarlyBeanReference method, AOP * related logic is executed. If the bean is not intercepted by AOP, getEarlyBeanReference returns the * bean as is, so you can put the *returnGetEarlyBeanReference (beanName, MBD, bean) * equivalent to: *returnbean; * /returngetEarlyBeanReference(beanName, mbd, bean); }}); } Object exposedObject = bean; / /... // use populateBean(beanName, MBD, instanceWrapper); / /... // Return the bean instancereturnexposedObject; } protected void addSingletonFactory(String beanName, ObjectFactory<? > singletonFactory) { synchronized (this.singletonObjects) {if(! This. SingletonObjects. Either containsKey (beanName)) {/ / add singletonFactory to singletonFactories cache this.singletonFactories.put(beanName, singletonFactory); / / removed from the other cached records, even without this. EarlySingletonObjects. Remove (beanName); this.registeredSingletons.add(beanName); }}}Copy the code

The main logic of the method is as follows

  1. Create the target object with createBeanInstance
  2. Add the object to the singletonFactories cache
  3. PopulateBean injects dependencies

conclusion

When Spring instantiates a bean, it creates the current bean object, puts it in the cache, and then retrieves the dependent properties recursively. When a property is injected, the dependent object is fetched from the cache if a circular dependency occurs.