Spring obtains single instance process

After reading this article, you will get

  • In the getBean method, Spring handles aliases and the name of factoryBean
  • How Spring gets beans from multi-level cache according to beanName
  • How Spring deals with getting common beans and factorybeans from users

introduction

from Initialization of Spring container In, we learned how Spring converts XML files to BeanDefinition and registers them with beandefinitionregtry.

Today, let's continue to learn about Spring's bean loading

public static void main(String[] args) {
        Resource resource = new ClassPathResource("coderLi.xml");
        DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
        xmlBeanDefinitionReader.loadBeanDefinitions(resource);
    }
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean class="com.demo.data.Person">
        <description>
            //Wechat search: CoderLi
        </description>
    </bean>
</beans>

Familiar taste, familiar formula

Source code analysis

We can add the following code to the Java code above

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person#0"));

We get the bean object Person from the Spring container according to the default bean name. Of course, we can use the default alias to get

System.out.println(defaultListableBeanFactory.getBean("com.demo.data.Person"));

If you are not familiar with Spring alias, you can read my article first Spring-AliasRegistry

We directly enter the AbstractBeanFactory ා getBean (string) method. AbstractBeanFactory is the parent class of DefaultListableBeanFactory

@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

You can see that the first thing to do is the real boss, abstractbeanfactory ා dogetbean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

   // Find the bean name of this parameter
   final String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   // Check whether there is this bean in the buffer and whether there is only a single bean in spring
   Object sharedInstance = getSingleton(beanName);
   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);
   }
 ..........
 ...........

Because this method is too long, we will take a part of it and analyze it step by step

transformedBeanName(name) This method is to name Convert to real beanName,Because the parameter we passed in might be a alias Or maybe it's a factoryBean Of beanName (Prefix is&),And we are Spring Stored in factoryBean Of beanName Yes, no & Prefix, so we need to deal with this prefix

protected String transformedBeanName(String name) {
   return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
public static String transformedBeanName(String name) {
        Assert.notNull(name, "'name' must not be null");
        // Is it a factory bean? If not, return it directly
        if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
            return name;
        }
        // If so, remove the prefix
        return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
            do {
                beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
            }
            while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
            return beanName;
        });
    }
    /**
     * Find the final bean Name of this alias, if not(
     * That is to say, the name in the parameter is someone else's bean name, then directly return this parameter.)
     *
     */
    public String canonicalName(String name) {
        String canonicalName = name;
        // Handle aliasing...
        String resolvedName;
        do {
            resolvedName = this.aliasMap.get(canonicalName);
            if (resolvedName != null) {
                canonicalName = resolvedName;
            }
        }
        while (resolvedName != null);
        return canonicalName;
    }

Let's take a look at the next method, defaultsingletonbeanregistry × getsingleton (string)

public Object getSingleton(String beanName) {
   // allowEarlyReference allows early dependencies
   return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {

   Object singletonObject = this.singletonObjects.get(beanName);
   // This bean is in the creation phase
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // concurrency control 
      synchronized (this.singletonObjects) {
         // Whether single instance cache exists
         singletonObject = this.earlySingletonObjects.get(beanName);
         // Whether to run the bean created by get bean factory
         if (singletonObject == null && allowEarlyReference) {
            // Get ObjectFactory in cache
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               // Caching objects into the earlySingletonObject
               this.earlySingletonObjects.put(beanName, singletonObject);
               // Remove from factory buffer
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

The above code is Spring trying to load the singleton from the cache. A single instance will only be created once in the same container of Spring, and then the bean will be retrieved directly from the cache.

Before introducing this method, let's recognize the member variables in the DefaultSingletonBeanRegistry class

  • Map < string, Object > singletonobjects. It's easy to understand that key is bean name and value is bean instance
  • Map < string, objectfactory <? >
  • Map < string, Object > earlySingletonObjects key is bean name and value is bean. But unlike singleton objects, when a bean is added to earlySingletonObjects, it is still in a state of being created. The purpose is very simple. Spring is used to solve the circular dependency in some scenarios

Let's go back to the code and analyze its logic

  1. First, try to get the bean from singleton objects. Here is the bean that has been created. If you can know it here, it is the best
  2. If we can't find it here, we need to determine whether the bean corresponding to this bean name is being created
  3. If so, let's see if the bean we are creating has been exposed. If not, let's see if our parameters allow us to rely on earlier beans
  4. If early dependencies are allowed, we will try to get the corresponding bean from ObjectFactory, put it into earlySingletonObjects, and remove it from singleton factories

Design similar to multilevel cache

In the above method, we can see the method is singleton currentlycreation (bean name)

public boolean isSingletonCurrentlyInCreation(String beanName) {
   return this.singletonsCurrentlyInCreation.contains(beanName);
}

Singletons currently in creation: before creating a bean, the bean name corresponding to it will be put into the Set. Later analysis will be involved. Let's talk about it first

debug

It's normal for us to get this bean for the first time and return null

So if we get bean twice in the code

defaultListableBeanFactory.getBean("com.demo.data.Person#0")
defaultListableBeanFactory.getBean("com.demo.data.Person#0")

Then the value returned for the second call is not null

 Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      // 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);
   }

Let's first assume that sharedInstance is not null, that is, we call getBean for the second time, and we enter getObjectForBeanInstance method

protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

    // Wang Dashi just wants a factory bean
   if (BeanFactoryUtils.isFactoryDereference(name)) {

      // If this is a NullBean type, it means that this is a null instance, and it directly returns
      if (beanInstance instanceof NullBean) {
         return beanInstance;
      }
      // The obtained beanInstance is not a factory, but your tm name has this & it's confusing, brother
      if (!(beanInstance instanceof FactoryBean)) {
         throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
      }

      if (mbd != null) {
         mbd.isFactoryBean = true;
      }
      return beanInstance;
   }

    // Wang didn't want the factory bean, and spring also helped him find a common bean and return it directly
   if (!(beanInstance instanceof FactoryBean)) {
      return beanInstance;
   }

   // Wang Dashi wants a common bean, but spring finds a factory bean for him. Does spring need to do some extra processing to return a common bean to Wang Dashi
   Object object = null;

   if (mbd != null) {
      mbd.isFactoryBean = true;
   } else {
      // See if there is any in the cache
      object = getCachedObjectForFactoryBean(beanName);
   }

   // If there is no bean factory
   if (object == null) {
      // Return bean instance from factory.
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      // Get bean definition from cache
      if (mbd == null && containsBeanDefinition(beanName)) {
         mbd = getMergedLocalBeanDefinition(beanName);
      }
      // Whether it is user-defined or the bean that the program itself needs to create
      boolean synthetic = (mbd != null && mbd.isSynthetic());

      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
   }
   return object;
}

Let's analyze the above code step by step

  1. If we call the name in getBean(name), if it contains the prefix &, on the surface that we want to get a FactoryBean from Spring, then we need to judge whether the beanInstance we get from the cache is a FactoryBean, if it is, we will directly return it. If it is not, we will throw an exception
  2. What we want is a non factoryBean and found the non factoryBean bean in the spring container, then return it directly
  3. What we want is a non factoryBean, but we find a factoryBean bean in the spring container, so we need to enter the getObjectFromFactoryBean method
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
      // Single instance mode and existing in cache
   if (factory.isSingleton() && containsSingleton(beanName)) {

      synchronized (getSingletonMutex()) {
         // Get the specified bean from the cache (this bean is created from the factory bean)
         Object object = this.factoryBeanObjectCache.get(beanName);

         if (object == null) {
            // If it is empty, get the object from the factory bean
            object = doGetObjectFromFactoryBean(factory, beanName);
            // Get from cache
            Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
            if (alreadyThere != null) {
               // It has been stored in the cache, and subsequent operations are not needed
               object = alreadyThere;
            } else {
               // Some post-processing is needed
               if (shouldPostProcess) {
                  // If this bean is being created
                  if (isSingletonCurrentlyInCreation(beanName)) {
                     return object;
                  }
                  // The preprocessing is mainly to add this bean to the queue singletons currently creating
                  beforeSingletonCreation(beanName);
                  try {
                     // Postprocessing objects obtained from factoryBean
                     // The generated object will be exposed to the bean reference and called back to beanPostProcessor
                     object = postProcessObjectFromFactoryBean(object, beanName);
                  } catch (Throwable ex) {
                     throw new BeanCreationException(beanName,
                           "Post-processing of FactoryBean's singleton object failed", ex);
                  } finally {
                     // Postprocessing removes it from singletons currentlycreation
                     afterSingletonCreation(beanName);
                  }
               }
               // His factory bean already exists in the cache, so the bean produced by this factory bean should also be cached
               if (containsSingleton(beanName)) {
                  this.factoryBeanObjectCache.put(beanName, object);
               }
            }
         }

         return object;
      }
   } else {
      // Not a single case
      Object object = doGetObjectFromFactoryBean(factory, beanName);

      if (shouldPostProcess) {
         try {
            //
            object = postProcessObjectFromFactoryBean(object, beanName);
         } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
         }
      }
      return object;
   }
}

Ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah, ah

  1. The first step is to determine whether the factoryBean is a singleton, if not, and if it is a user-defined bean, then you need to call the postProcessObjectFromFactoryBean method to do a subsequent processing

    1. In this case, the final callback is our commonly used interface, BeanPostProcessor

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)

           throws BeansException {

       Object result = existingBean;
       for (BeanPostProcessor processor : getBeanPostProcessors()) {
           Object current = processor.postProcessAfterInitialization(result, beanName);
           if (current == null) {
               return result;
           }
           result = current;
       }
       return result;
   }
 ```
  1. If this beanFactory is a single example, let's see if this bean exists in the Map of factorybeanobjectcache (key is beanName, value is the object generated by beanFactory and the bean we are going to get)
  2. If it exists, it will return directly. If it does not exist, it will doGetObjectFromFactoryBean. From this method, use factorybean ා GetObject to generate beans
  3. In fact, the following code is really confusing

    Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    if (alreadyThere != null) {
    // It has been stored in the cache, and subsequent operations are not needed
    object = alreadyThere;
    }
  4. Then we see the beforesingleton creation method, which is isssingleton currentlycreation in getSingleton above to determine whether a bean is being created

    protected void beforeSingletonCreation(String beanName) {
            if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
        }
        public boolean isSingletonCurrentlyInCreation(String beanName) {
            return this.singletonsCurrentlyInCreation.contains(beanName);
        }
  5. Then we call the postProcessObjectFromFactoryBean method, and the final callback is our common interface, BeanPostProcessor
  6. It is best to call the aftersingleton creation (bean name) method, remove it from the collection of beans being created, and finally add it to the factorybean objectcache collection

Today, we will first analyze here, and then we will continue to discuss in the following articles. Today, we have roughly analyzed these three methods in getBean

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

This is the general process of the above three methods. I hope they can help you

Interested in group chat, communication and rowing together

Keywords: Java Spring xml encoding

Added by SirEddie on Sat, 06 Jun 2020 08:47:18 +0300