What are circular dependencies?

Circular dependencies are formed when multiple beans depend on each other, such as beans A and B. There is property B in A and property A in B. When Spring instantiates A, it finds property B in A and then de-instantiates B. When it instantiates B, it also finds property A.

Spring family bucket learning notes + big factory interview real questions to share!

How does Spring address loop dependencies?

Spring uses various Bean intermediate states to inject beans into properties of dependent beans before they are instantiated. That Bean has three states are young state (l1), embryo state (the second level cache), small tadpoles state (3) the youth state on behalf of the Bean is instantiated to complete, can be used directly, embryonic state representative Bean already exist but also in creating, has not been created and small tadpoles state representative haven’t started to create, But it can be created at any time. The three states are like three levels, and you can step up from the tadpole state to the embryonic state and then to the young-adult state. Then Spring starts to create Bena and stores the Bean in the tadpole state cache in advance. Beans in the tadpole state cache collection are used when circular dependencies are found, in advance

The case of circular dependencies

Suppose there are four beans BeanA, BeanB, BeanC and BeanD in the example, where

  • BeanA depends on BeanB,C
  • BeanB depends on BeanC
  • BeanC depends on BeanA
  • BeanD depends on BeanA,B,C

BeanA beanA = beanFactory.getBean(“beanA”,BeanA.class); BeanB beanB = beanFactory.getBean(“beanB”,BeanB.class); BeanC beanC = beanFactory.getBean(“beanC”,BeanC.class); BeanD beanD = beanFactory.getBean(“beanD”,BeanD.class);

So what does their instantiation process look like?

As you can see, Spring’s solution to loop dependencies is caching.

Code parsing (only relevant code is retained)

1. Check whether the Bean has been instantiated in the cache

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

   // First check if level 1 cache exists
   Object singletonObject = this.singletonObjects.get(beanName);

   / * * * if level cache does not exist in the current Bean has not yet been created or are creating is * check whether the current Bean is created in a state of deposit (when Bean creation will Bean name to singletonsCurrentlyInCreation collection) * /
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         // Check whether the level 2 cache exists
         singletonObject = this.earlySingletonObjects.get(beanName);

         /** * @@ If the secondary cache does not exist and allows the use of early dependencies * allowEarlyReference: what is early dependency? * is used in advance when a Bean is not yet mature. In the instantiation flowchart, we see the state of the Bean when it is just instantiated before the cache is added but not yet injected
         if (singletonObject == null && allowEarlyReference) {
            
            // Get the Bean ObjectFactory from the level 3 cache
            ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
            // If the Bean's ObjectFactory exists
            if(singletonFactory ! =null) {
               // Get an instance of the Bean using the getObject method
               singletonObject = singletonFactory.getObject();
               // Upgrade the bean from level 3 cache to level 2 cache
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

2. Create a Bean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   /** * We first get the beans from the level 3 cache using the getSingleton() method. This is where we add the beans to the level 3 cache. Check whether the current bean is singleton and allows circular references [Note 1], and whether it is currently being created (as added in the getSingleton method) * 2. If early exposure is allowed, the addSingletonFactory() method adds the current bean's ObjectFactory * * to the cache: ObjectFactory * throws BeanCreationException when a BeanCreationException occurs: BeanCreationException = BeanCreationException = BeanCreationException = BeanCreationException = BeanCreationException The implication of early exposure is that beans in their creation state are placed in the specified cache while they are still being created, providing support for circular dependencies */
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   // Pre-exposure is required
   if (earlySingletonExposure) {
      /** * Adds the current bean's ObjectFactory */ to the cache (level 3 cache)
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   /** * Initialize the bean instance. */
   Object exposedObject = bean;
   try {
      /** * Dependency injection will recursively call getBean */
      populateBean(beanName, mbd, instanceWrapper);
      // Call an initialization method, such as init-method
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   
   /** * Enter judgment when allowing early exposure * @ What is done here? * 1. Check if the current bean undergoes a loop dependency * - Get the bean from the cache with getSingleton(beanName,false), Passing false does not fetch beans * from the tertiary cache - why check to see if the current bean undergoes a cyclic dependency? Since passing false means not fetching level 3 caches * - what is the case with level 1 and level 2 caches? The answer is after a cyclic dependency [explanation 1] and after bean instantiation * - so if the bean returned by getSingleton is not empty, then the bean has just undergone a cyclic dependency * * 2. If (exposedObject == bean) is the same as if (exposedObject == bean). If (exposedObject == bean) is the same as if (exposedObject == bean) is the same as if (exposedObject == bean). When is the bean cached from stored in? The addSingletonFactory method (line 649) * - then the beans stored there are only pre-exposed beans, which have not yet been injected or initialized, but which can change the instance of the current bean directly during injection and initialization. What does that mean? This means that a bean that has undergone dependency injection and initialization is probably not the same bean at all as the bean in the cache. * Not very well understood, direct assignment, but after various BeanPostProsser or dependency injection and initialization is not the same * 2.2 inconsistent: If A is in the level 3 cache, then A is in the level 3 cache. If A is in the level 3 cache, then B is in the level 3 cache. The bean in the level-3 cache is then fetched and A is promoted from level-3 to level-2. Instantiation B is complete, followed by instantiation A. @ extensively on * * * assume our business to a data cache, assuming that the I in the cache storage has a value of 1, when I put the I in the database value to 2, the cache at this moment I have not been changed or 1 * data already and our actual data deviation, inconsistent, at this time there are two solutions: 1. The server detects data inconsistency and throws an exception. (that is, into the else if block) * 2. Directly using the original value is 1 (i.e., change allowRawInjectionDespiteWrapping to true), and of course the two ways is obviously not our normal database operation, Just * to illustrate the current example. * * /
   if (earlySingletonExposure) {
      // Get the bean corresponding to beanName in the cache (except the tertiary cache)
      Object earlySingletonReference = getSingleton(beanName, false);
      // The earlySingletonReference is not empty after a cyclic dependency
      if(earlySingletonReference ! =null) {
         // If exposedObject is not changed in the initialization method, it is not enhanced
         if (exposedObject == bean) {
            // Direct assignment? But not after various BeanPostProsser or dependency injection and initialization
            exposedObject = earlySingletonReference;
         }
         /** ** else if the current Bean is enhanced by BeanPostProessor If the enhanced * is allowed 2. Check if there are any beans that depend on the current bean * * If there are dependent beans that have already been instantiated, then throw an exception * Why throw an exception? * Because the bean that depends on the current bean has been internally injected with an old version of the current bean, but the version of the bean has become new after the initialization method * the old one is no longer applicable, so an exception is thrown * */
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if(! actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); }}}}try {
      /** * Register bean as disposable
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}
Copy the code