Spring obtains single instance process

After reading this article, you will get

  • How to do circular dependency detection for prototype type bean s in Spring
  • How to do circular dependency detection for singleton type bean s in Spring

preface

Follow up on the previous article Spring obtains single instance process (1) Let's continue to analyze the following process this time

As we said in the previous article, first we find the corresponding bean name according to the name, and then we go to the cache to see whether the corresponding bean has been created / created. If we find the bean in the cache, we need to deal with the bean. For example, the user wants a common bean, but in Spring What is found in the cache is a factoryBean. At this time, we need to call the getObject method of the factoryBean and some corresponding pre methods and callbacks.

So what about the process if we can't find the bean in the cache? This is what this article is going to share with you

Source code analysis

if (sharedInstance != null && args == null) {
   // I deleted some spring log s here
   // Deal with the situation of factory beans, including getting from the cache of factory beans, or calling the get bean method of factory beans again, including some callbacks
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
   // From the getSingleton above, you can't get the bean of the object, which indicates that the scope of the bean is not singleton. If the bean is singleton, it doesn't initialize a sentence
     //  Because Spring only solves circular dependency in singleton mode, if there is circular dependency in prototype mode, an exception will be thrown
   // The loop dependency check here uses threadLocal because the prototype type is just
   if (isPrototypeCurrentlyInCreation(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }

    // If not found in the container, load from the parent container
   BeanFactory parentBeanFactory = getParentBeanFactory();
   // parentBeanFactory is not empty and BeanDefinition of the name does not exist in beanDefinitionMap
   if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
      // Not found -> check parent.
      // This is just to find out his real beanName without removing the prefix of factory bean
      String nameToLookup = originalBeanName(name);
      if (parentBeanFactory instanceof AbstractBeanFactory) {
         return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
               nameToLookup, requiredType, args, typeCheckOnly);
      } else if (args != null) {
         // Delegation to parent with explicit args.
         return (T) parentBeanFactory.getBean(nameToLookup, args);
      } else if (requiredType != null) {
         // No args -> delegate to standard getBean method.
         return parentBeanFactory.getBean(nameToLookup, requiredType);
      } else {
         return (T) parentBeanFactory.getBean(nameToLookup);
      }
   }
  .......
  .......
  ........
}

The first step is to determine whether this is a prototype type bean. If it is and is being created, an exception of circular dependency will be thrown

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
  // Prototype currentlycreation is a threadLocal
   Object curVal = this.prototypesCurrentlyInCreation.get();
   return (curVal != null &&
         (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

When creating a prototype bean, the following method will be called

protected void beforePrototypeCreation(String beanName) {
   Object curVal = this.prototypesCurrentlyInCreation.get();
   if (curVal == null) {
      this.prototypesCurrentlyInCreation.set(beanName);
   } else if (curVal instanceof String) {
      Set<String> beanNameSet = new HashSet<>(2);
      beanNameSet.add((String) curVal);
      beanNameSet.add(beanName);
      this.prototypesCurrentlyInCreation.set(beanNameSet);
   } else {
      Set<String> beanNameSet = (Set<String>) curVal;
      beanNameSet.add(beanName);
   }
}

curVal Or a String Or a Set , While creating prototype bean After completion

protected void afterPrototypeCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        if (curVal instanceof String) {
            this.prototypesCurrentlyInCreation.remove();
        } else if (curVal instanceof Set) {
            Set<String> beanNameSet = (Set<String>) curVal;
            beanNameSet.remove(beanName);
            if (beanNameSet.isEmpty()) {
                this.prototypesCurrentlyInCreation.remove();
            }
        }
    }

You can see that Spring uses ThreadLocal to do a circular dependency detection. We also mentioned it in the source code analysis of Spring resource loading, and also used ThreadLocal to do a circular reference detection of resources Initialization of Spring container

The second step is to determine whether the current beanFactory has a parent container (parent beanFactory). If there is and the beanDefinition of beanName does not exist in the current beanFactory, then try to get the bean in the parent container

Let's move on to the following code

// If it is not just for type checking, it is to create a bean. Mark that the bean has been created or will be created
if (!typeCheckOnly) {
   markBeanAsCreated(beanName);
}

try {

   //  Get the GenericBeanDefinition corresponding to beanName from the container and convert it to RootBeanDefinition
   final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
   //  Check the BeanDefinition of the given merge
   checkMergedBeanDefinition(mbd, beanName, args);

   // Guarantee initialization of beans that the current bean depends on.
   // Processing dependent bean s
   String[] dependsOn = mbd.getDependsOn();

   if (dependsOn != null) {

      for (String dep : dependsOn) {
         // If it's circular dependency
         if (isDependent(beanName, dep)) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
         }
         // register
         registerDependentBean(dep, beanName);
         try {
            // Look at the big guy I depend on
            getBean(dep);
         } catch (NoSuchBeanDefinitionException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
         }
      }
   }
  ......
  ......

The third step is to get the registered BeanDefinition from BeanDefinitionRegistry, and then get other beans that the bean depends on, traverse the beans it depends on, and determine whether it has circular dependency

protected boolean isDependent(String beanName, String dependentBeanName) {
   synchronized (this.dependentBeanMap) {
      return isDependent(beanName, dependentBeanName, null);
   }
}

private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {

   if (alreadySeen != null && alreadySeen.contains(beanName)) {
      return false;
   }

   String canonicalName = canonicalName(beanName);
   // Find the set that depends on this bean name
   Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);

   // No one depends on this beanName
   if (dependentBeans == null) {
      return false;
   }

   // Ooh, bean depends on bean, bean depends on bean, and the end
   if (dependentBeans.contains(dependentBeanName)) {
      return true;
   }

   // See if other dependencies that depend on beanName are dependent on dependentBeanName
   // A wants to rely on F. BCDE depends on A. now we have come to this step and have determined that F does not depend on A. then we need to see if F depends on BCDE. If it depends, it is circular dependence
   for (String transitiveDependency : dependentBeans) {
      if (alreadySeen == null) {
         alreadySeen = new HashSet<>();
      }
      alreadySeen.add(beanName);
      if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
         return true;
      }
   }
   return false;
}

Before each bean is created, its dependency will be registered, which is mainly composed of two maps. One is the key as the dependent, the value as the dependent collection, the other is the key as the dependent, and the value as the dependent collection. For example, beanA depends on beanB and beanC

key For the dependent value Set for dependents
beanB ---> beanA
beanC ---> beanA
key For the dependent, value Set for dependents
beanA ---> beanB,beanC

The fourth step is to register the dependency, that is, to store the data in the above two maps

public void registerDependentBean(String beanName, String dependentBeanName) {
        String canonicalName = canonicalName(beanName);
        // Add this person who depends on me to this
        synchronized (this.dependentBeanMap) {
            Set<String> dependentBeans =
                    this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));

            if (!dependentBeans.add(dependentBeanName)) {
                return;
            }
        }

        // Put the big guy I depend on in the list I depend on
        synchronized (this.dependenciesForBeanMap) {
            Set<String> dependenciesForBean =
                    this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
            dependenciesForBean.add(canonicalName);
        }
    }

The last getBean is back to where we started

getBean(dep);

Today, we will first analyze here, and then we will continue to discuss in the following articles. Today we have a general analysis

summary

  • Find the corresponding beanName according to the name in the parameter, whether the name is an alias or the beanName of a factoryBean
  • Check whether this beanName object is included in the cache

    • First, check whether there are singleton objects in the first level cache
    • Then from the second level cache, earlySingletonObjects
    • If none of them are available, check whether there are any from the singleton factors of the three-level cache
  • If there is a bean in the cache, we still need to deal with it

    • If the bean returned in the Spring cache is factoryBean, and the user also wants a beanFactory (the prefix in parameter name is &), then we directly return
    • If the returned bean in the Spring cache is a normal bean and the user wants a normal bean, return it directly
    • If the Bean returned in the Spring cache is a factoryBean, and what the user wants is a normal bean, then we need to get this bean from factoryBean
    • In the process of getting this bean from factoryBean, we need to call the preprocessing, postprocessing and our common interface callback BeanPostProcessor
  • If there is no bean in the cache, judge whether it is of prototype type and loop dependency
  • If not, try to find the bean in the parent container
  • If the parent container does not have one, obtain the beanDefinition corresponding to the beanName to find out the dependent beanName
  • Determine whether the beanName and the dependent beanName are circular dependencies. If not, register their dependencies and call the getBean method to create the dependent beanName

Tags: Java Spring

Posted on Fri, 05 Jun 2020 22:40:07 -0700 by jini01