The little-known details in the IoC container of Spring's core principles

This article is excerpted from Spring 5 core principles

The Spring IoC container also has some advanced features, such as pre initializing beans using the lazy init attribute, generating or modifying Bean objects using factorybeans, and managing Bean declaration cycle events using the BeanPostProcessor post processor during Bean initialization.

1 about delayed loading

We already know that the initialization process of the IoC container is to locate, load and register the Bean definition resources. At this time, the dependency injection of the container to the Bean does not occur. The dependency injection is completed through the getBean() method when the application requests the Bean from the container for the first time.
When the lazy init = false attribute is configured in the < Bean > element of the Bean definition resource, the container will pre instantiate the configured Bean during initialization, and the dependency injection of the Bean has been completed when the container is initialized. In this way, when the application requests the managed Bean from the container for the first time, it does not need to initialize and inject dependency on the Bean, but directly obtain the Bean that has completed dependency injection from the container, which improves the performance of the application to obtain the Bean from the container for the first time.

1.1. refresh() method

The IoC container reads the located Bean definition resources from the refresh() method. We start with the refresh() method of the AbstractApplicationContext class and review the source code:

@Override
public void refresh() throws BeansException, IllegalStateException {
	  ...
      //The subclass's refreshBeanFactory() method starts
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	  ...
}

In the refresh() method, ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); Started the loading and registration process of Bean definition resources. The finishBeanFactoryInitialization() method is the place to process the beans pre instantiated in the registered Bean definition (lazy init = false, Spring pre instantiates by default, that is, true).

1.2. Use finishBeanFactoryInitialization() to process pre instantiated beans

After the Bean definition resource is loaded into the IoC container, the container parses the Bean definition resource into the data structure BeanDefinition inside the container and registers it in the container. The finishBeanFactoryInitialization() method in the AbstractApplicationContext class pre initializes the Bean configured with pre instantiation attribute. The source code is as follows:

//Pre instantiate the Bean configured with the lazy init attribute
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   //This is the newly added code of Spring 3, which specifies a conversion service for the container
   //Used when converting some Bean properties
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders (strVal));
   }

   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   //To match the types, stop using the temporary class loader
   beanFactory.setTempClassLoader(null);

   //Cache all registered BeanDefinition metadata in the container to prevent modification
   beanFactory.freezeConfiguration();

   //Pre instantiate beans in singleton mode configured with lazy init attribute
   beanFactory.preInstantiateSingletons();
}

ConfigurableListableBeanFactory is an interface, and the preInstantiateSingletons() method is provided by its subclass DefaultListableBeanFactory.

1.3. Pre instantiation of beans in singleton mode configured with lazy init attribute

The pre instantiation related source code of beans in singleton mode configured with lazy init attribute is as follows:

public void preInstantiateSingletons() throws BeansException {
   if (this.logger.isDebugEnabled()) {
      this.logger.debug("Pre-instantiating singletons in " + this);
   }

   List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

   for (String beanName : beanNames) {
      //Gets the Bean definition with the specified name
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      //The Bean is not abstract, but in singleton mode, and the lazy init attribute is configured as false
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         //If the Bean with the specified name is the Bean that creates the container
         if (isFactoryBean(beanName)) {
            //FACTORY_ Bean_ Prefix = "&", when Bean name is preceded by "&"
            //When, you get the container object itself, not the Bean generated by the container
            //Call getBean method to trigger Bean instantiation and dependency injection
            final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
            //Identifies whether pre instantiation is required
            boolean isEagerInit;
            if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
               //An anonymous inner class
               isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
                     ((SmartFactoryBean<?>) factory).isEagerInit(),
                     getAccessControlContext());
            }
            else {
               isEagerInit = (factory instanceof SmartFactoryBean &&
                     ((SmartFactoryBean<?>) factory).isEagerInit());
            }
            if (isEagerInit) {
               //Call the getBean() method to trigger Bean instantiation and dependency injection
               getBean(beanName);
            }
         }
         else {
            getBean(beanName);
         }
      }
   }

Through the analysis of the lazy init processing source code, it can be seen that if the lazy init attribute is set, the container will trigger the initialization and dependency injection of the specified Bean through the getBean() method after completing the registration of the Bean definition. As mentioned earlier, when the application requests the required Bean from the container for the first time, the container no longer needs to initialize and dependency inject the Bean. You can directly take an existing Bean from the beans that have completed instantiation and dependency injection, which improves the ability to obtain the Bean for the first time.

2 about FactoryBean and BeanFactory

In Spring, there are two classes that are easily confused: BeanFactory and FactoryBean.
BeanFactory: a Bean Factory is a Factory. The highest level interface of the Spring IoC container is BeanFactory, which is used to manage beans, that is, instantiate, locate and configure objects in applications and establish dependencies between these objects.
FactoryBean: a factory Bean is a Bean that generates other Bean instances. This Bean has no special requirements. It only needs to provide a factory method, which is used to return other Bean instances. Under normal circumstances, beans do not need to implement the factory pattern by themselves, and the Spring container acts as the factory; In a few cases, the Bean in the container itself is a factory, which is used to generate other Bean instances.
When using a container, you can use the escape character "&" to get the FactoryBean itself to distinguish the instance object generated through the FactoryBean from the FactoryBean object itself. The escape character is defined in BeanFactory through the following code:
String FACTORY_BEAN_PREFIX = "&";

If myJndiObject is a FactoryBean, use & myJndiObject to get the myJndiObject object instead of the object generated by myJndiObject.

2.1. FactoryBean source code

//Factory Bean, used to generate other objects
public interface FactoryBean<T> {

   //Get container managed object instance
   @Nullable
   T getObject() throws Exception;

   //Gets the type of the object created by the Bean factory
   @Nullable
   Class<?> getObjectType();

   //Whether the object created by the Bean factory is in singleton mode. If so,
   //Then there is only one instance object in the whole container, and each request returns the same instance object
   default boolean isSingleton() {
      return true;
   }

}

2.2. getBean() method of abstractbeanfactory

When analyzing the source code of instantiation Bean and dependency injection of Spring IoC container, it is mentioned that doGetBean() method of AbstractBeanFactory will be called when getBean() method triggers container instantiation Bean. Its important source code is as follows:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
      ...
      BeanFactory parentBeanFactory = getParentBeanFactory();
      //The parent container of the current container exists, and the Bean with the specified name does not exist in the current container
      if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
         //Resolves the original name of the specified Bean name
         String nameToLookup = originalBeanName(name);
         if (parentBeanFactory instanceof AbstractBeanFactory) {
            return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
                  nameToLookup, requiredType, args, typeCheckOnly);
         }
         else if (args != null) {
            //The delegate parent container looks up based on the specified name and explicit parameters
            return (T) parentBeanFactory.getBean(nameToLookup, args);
         }
         else {
            //The delegate parent container looks up the by the specified name and type
            return parentBeanFactory.getBean(nameToLookup, requiredType);
         }
      }
   ...
   return (T) bean;
}

//Get the instance object of a given Bean, mainly to complete the relevant processing of FactoryBean
protected Object getObjectForBeanInstance(
      Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

   //The container has obtained the Bean instance object, which may be an ordinary Bean,
   //It may also be a factory Bean. If it is a factory Bean, use it to create a Bean instance object,
   //If the call itself wants to get a reference to a container, the factory Bean instance object is returned
   //If the specified name is the dereference of the container (that is, the object itself rather than the memory address)
   //And the Bean instance is not the factory Bean that creates the Bean instance object
   if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
      throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
   }

   //If the Bean instance is not a factory Bean, or the specified name is a dereference of the container
   //When the caller gets the reference to the container, it directly returns the current Bean instance
   if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
      return beanInstance;
   }

   //Handle the dereference that the specified name is not a container, or the Bean instance object obtained according to the name is a factory Bean
   //Use the factory Bean to create an instance object of the Bean
   Object object = null;
   if (mbd == null) {
      //Gets the Bean instance object with the specified name from the Bean factory cache
      object = getCachedObjectForFactoryBean(beanName);
   }
   //Let the Bean factory produce the Bean instance object with the specified name
   if (object == null) {
      FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
      //If the Bean produced from the Bean factory is in singleton mode, the cache
      if (mbd == null && containsBeanDefinition(beanName)) {
         //Get the Bean definition with the specified name from the container. If it inherits the base class, merge the related properties of the base class
         mbd = getMergedLocalBeanDefinition(beanName);
      }
      //If the Bean definition information is obtained from the container and the Bean definition information is not fictitious,
      //Then let the factory Bean produce Bean instance objects
      boolean synthetic = (mbd != null && mbd.isSynthetic());
      //Call the getObjectFromFactoryBean() method of the FactoryBeanRegistrySupport class
      //Implement the process of factory Bean producing Bean instance objects
      object = getObjectFromFactoryBean(factory, beanName, !synthetic);
   }
   return object;
}

In the getObjectForBeanInstance() method above to obtain the instance object of a given Bean, the getObjectFromFactoryBean() method of the FactoryBean- RegistrySupport class will be called, which implements the Bean instance object produced by the Bean factory.

2.3. AbstractBeanFactory production Bean instance object

The main source code of the Bean instance object produced in the AbstractBeanFactory class is as follows:

//The Bean factory produces Bean instance objects
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
   //The Bean factory is a singleton pattern, and there is a Bean instance object with the specified name in the Bean factory cache
   if (factory.isSingleton() && containsSingleton(beanName)) {
      //Multi thread synchronization to prevent data inconsistency
      synchronized (getSingletonMutex()) {
         //Get the Bean instance object with the specified name directly from the cache of the Bean factory
         Object object = this.factoryBeanObjectCache.get(beanName);
         //If there is no instance object with the specified name in the Bean factory cache, the instance object is produced
         if (object == null) {
            //Call the method of obtaining the object of the Bean factory to produce the instance object of the specified Bean
            object = doGetObjectFromFactoryBean(factory, beanName);
            Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
            if (alreadyThere != null) {
               object = alreadyThere;
            }
            else {
               if (shouldPostProcess) {
                  try {
                     object = postProcessObjectFromFactoryBean(object, beanName);
                  }
                  catch (Throwable ex) {
                     throw new BeanCreationException(beanName,
                           "Post-processing of FactoryBean's singleton object failed", ex);
                  }
               }
               //Add the production instance object to the cache of the Bean factory
               this.factoryBeanObjectCache.put(beanName, object);
            }
         }
         return object;
      }
   }
   //Call the method of obtaining the object of the Bean factory to produce the instance object of the specified Bean
   else {
      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;
   }
}

//Call the method of the Bean factory to produce the instance object of the specified Bean
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
      throws BeanCreationException {

   Object object;
   try {
      if (System.getSecurityManager() != null) {
         AccessControlContext acc = getAccessControlContext();
         try {
            //Anonymous inner class that implements the privilegedexception action interface
            object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () ->
                  factory.getObject(), acc);
         }
         catch (PrivilegedActionException pae) {
            throw pae.getException();
         }
      }
      else {
         //Call the BeanFactory interface to implement the object creation method of the class
         object = factory.getObject();
      }
   }
   catch (FactoryBeanNotInitializedException ex) {
      throw new BeanCurrentlyInCreationException(beanName, ex.toString());
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
   }

   //The created instance object is null, or null is returned because the singleton object is being created
   if (object == null) {
      if (isSingletonCurrentlyInCreation(beanName)) {
         throw new BeanCurrentlyInCreationException(
               beanName, "FactoryBean which is currently in creation returned null from getObject");
      }
      object = new NullBean();
   }
   return object;
}

From the above source code analysis, we can see that the BeanFactory interface calls the method of obtaining the object of its implementation class to realize the function of creating Bean instance object.

2.4. FactoryBean implements the method of the class to get the object

There are many implementation classes of FactoryBean interface, such as Proxy, RMI, JNDI, ServletContextFactoryBean, etc. The FactoryBean interface provides a good encapsulation mechanism for the Spring container. The specific methods to obtain objects are provided by different implementation classes according to different implementation strategies. Let's analyze the source code of the simplest AnnotationTestFactoryBean class:

public class AnnotationTestBeanFactory implements FactoryBean<FactoryCreatedAnnotationTestBean> {
   private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean();
   public AnnotationTestBeanFactory() {
      this.instance.setName("FACTORY");
   }
   @Override
   public FactoryCreatedAnnotationTestBean getObject() throws Exception {
      return this.instance;
   }
   //AnnotationTestBeanFactory generates the implementation of the Bean instance object
   @Override
   public Class<? extends IJmxTestBean> getObjectType() {
      return FactoryCreatedAnnotationTestBean.class;
   }
   @Override
   public boolean isSingleton() {
      return true;
   }
}

Proxy, RMI, JNDI and other implementation classes provide methods according to the corresponding strategies. We don't analyze them one by one here. This is not the core function of Spring. Interested "little partners" can conduct in-depth research by themselves.

3 repeat autowiring

The Spring IoC container provides two ways to manage Bean dependencies:
(1) Explicit Management: realize Bean dependency management through the attribute value and construction method of BeanDefinition.
(2) Autowiring: the Spring IoC container has the dependency auto assembly function. It does not need to explicitly declare the dependency of Bean attributes. It only needs to configure the autowiring attribute. The IoC container will automatically use reflection to find the type and name of the attribute, and then automatically match the beans in the container based on the type or name of the attribute, so as to automatically complete dependency injection.
The automatic assembly of container to Bean occurs during the dependency injection of container to Bean. When analyzing the dependency injection source code of the Spring IoC container, we already know that the dependency attribute injection of the container on the Bean instance object occurs in the populateBean() method of the AbstractAutoWireCapableBeanFactory class. The implementation principle of autowiring is analyzed through the program flow below.

3.1. AbstractAutoWireCapableBeanFactory performs attribute dependency injection on Bean instance objects

When an application requests a Bean from the IoC container for the first time through the getBean() method (except those configured with the lazy init pre instantiation attribute), the container creates a Bean instance object and performs attribute dependency injection on the Bean instance object. The populateBean() method of AbstractAutoWire- CapableBeanFactory implements the function of attribute dependency injection. Its main source code is as follows:

//Set the Bean property to the generated instance object
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
   //Gets the property value set by the container for the BeanDefinition when parsing the Bean definition
   PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

   //To handle dependency injection, first handle the dependency injection of autowiring automatic assembly
   if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
         mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

      //autowiring is automatically assembled according to the Bean name
      if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
         autowireByName(beanName, mbd, bw, newPvs);
      }

      //autowiring is automatically assembled according to Bean type
      if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
         autowireByType(beanName, mbd, bw, newPvs);
      }

      pvs = newPvs;
   }

   //Dependency injection for non autowiring attributes
   ...
}

3.2. Spring IoC container performs autowiring automatic attribute dependency injection according to Bean name or type

The Spring IoC container performs autowiring automatic attribute dependency injection according to Bean name or type. The important codes are as follows:

//Automatic dependency injection for attributes by type
protected void autowireByType(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

   //Gets a user-defined type converter
   TypeConverter converter = getCustomTypeConverter();
   if (converter == null) {
      converter = bw;
   }

   //Store resolved attributes to be injected
   Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
   //Handle non simple attributes in Bean objects (not simply inherited objects, for example, 8 primitive types, characters, URL s, etc. are simple attributes)
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   for (String propertyName : propertyNames) {
      try {
         //Gets the property descriptor for the specified property name
         PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
         //Do not perform autowiring automatic dependency injection on attributes of Object type
         if (Object.class != pd.getPropertyType()) {
            //Gets the assignment method of the property
            MethodParameter MethodParam = BeanUtils.getWriteMethodParameter(pd);
            //Checks whether the specified type can be converted to the type of the target object
            boolean eager = !PriorityOrdered.class.isInstance(bw.getWrappedInstance());
            //Create a dependency description to be injected
            DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(MethodParam, eager);
            //Resolve dependencies according to the Bean definition of the container and return all Bean objects to be injected
            Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
            if (autowiredArgument != null) {
               //Assign the property to the referenced object
               pvs.add(propertyName, autowiredArgument);
            }
            for (String autowiredBeanName : autowiredBeanNames) {
               //Register the dependent Bean name for the specified name attribute and inject the dependency of the attribute
               registerDependentBean(autowiredBeanName, beanName);
               if (logger.isDebugEnabled()) {
                  logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" 
                        + propertyName + "' to bean named '" + autowiredBeanName + "'");
               }
            }
            //Release automatically injected properties
            autowiredBeanNames.clear();
         }
      }
      catch (BeansException ex) {
         throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
      }
   }
}

From the above source code analysis, it can be seen that automatic dependency injection through attribute name is slightly simpler than automatic dependency injection through attribute type. But what really implements attribute injection is the registerDependentBean() method of the DefaultSingletonBeanRegistry class.

3.3. The registerDependentBean() method of defaultsingletonbeanregistry implements attribute dependency injection

The registerDependentBean() method of DefaultSingletonBeanRegistry implements the following important codes for attribute dependency injection:

//Inject a dependent Bean for the specified Bean
public void registerDependentBean(String beanName, String dependentBeanName) {
   //Handle the Bean name and convert the alias to the canonical Bean name
   String canonicalName = canonicalName(beanName);
   Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
   if (dependentBeans != null && dependentBeans.contains(dependentBeanName)) {
      return;
   }

   //Multi thread synchronization ensures the consistency of data in the container
   //Find the dependent Bean of the specified name Bean in the container through "Bean name → all dependent Bean name collection"
   synchronized (this.dependentBeanMap) {
      //Gets all dependent Bean names for the specified Bean name
      dependentBeans = this.dependentBeanMap.get(canonicalName);
      if (dependentBeans == null) {
         //Set dependent Bean information for Bean
         dependentBeans = new LinkedHashSet<>(8);
         this.dependentBeanMap.put(canonicalName, dependentBeans);
      }
      //Add Bean dependency information to the container through "Bean name → all dependent Bean name collection"
      //That is, add the Bean on which the Bean depends to the collection of the container
      dependentBeans.add(dependentBeanName);
   }
   //Find the dependent Bean of the specified name Bean in the container through "Bean name → dependent Bean collection of the specified name Bean"
   synchronized (this.dependenciesForBeanMap) {
      Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName);
      if (dependenciesForBean == null) {
         dependenciesForBean = new LinkedHashSet<>(8);
         this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean);
      }
      //Add Bean dependency information in the container through "Bean name → specify Bean dependency Bean name collection"
      //That is, add the Bean on which the Bean depends to the collection of the container
      dependenciesForBean.add(canonicalName);
   }
}

It can be seen that the implementation process of autowiring is as follows:
(1) Call the getBean() method on the attributes of the Bean to complete the initialization and dependency injection of the dependent Bean.
(2) Set the attribute reference of the dependent Bean to the dependent Bean attribute.
(3) Store the names of dependent beans and dependent beans in the collection of IoC containers.
autowiring automatic attribute dependency injection of Spring IoC container is a very convenient feature, which can simplify development configuration, but everything has two sides, and automatic attribute dependency injection also has shortcomings: first, the dependency of Bean can not be clearly seen in the configuration file, which will cause some difficulties in maintenance; Secondly, because automatic attribute dependency injection is automatically executed by the Spring container, the container will not judge intelligently. If it is not configured properly, it will bring unpredictable consequences. Therefore, comprehensive consideration is needed when using automatic attribute dependency injection.

This article is the original of "Tom bomb architecture". Please indicate the source for reprint. Technology lies in sharing, I share my happiness!
If this article is helpful to you, you are welcome to pay attention and praise; If you have any suggestions, you can also leave comments or private letters. Your support is the driving force for me to adhere to my creation.

It's not easy to be original. It's cool to insist. I've seen it here. Little partners remember to like, collect and watch it. Pay attention to it three times a button! If you think the content is too dry, you can share and forward it to your friends!

Keywords: Java Spring source code

Added by tippy_102 on Sat, 01 Jan 2022 17:20:58 +0200