4. Construction and registration of BeanDefinition

Foreword

In the last article, we talked about the source code of the processBeanDefinition(ele, delegate) method:

//This method requires two parameters: 1. The child node element parsed before, 2. The parser
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    //Here the element object is parsed into a BeanDefinitionHolder object. This BeanDefinitionHolder is equivalent to a layer of packaging for the BeanDefinition.
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
       //If there is a custom tag nested in the bean tag, it will be parsed through this method, the element will be parsed again, and encapsulated into the final bdHolder
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      try {
         //Here is to register BeanDdfinition, register it with BeanDefinitionReaderUtils tool class, and store it in a Map.
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Here is the publishing event: After the registration of the entire spring container is completed, publish the event and notify the spring container. Spring listens for this event. If it listens, it will perform subsequent processing.
      //But this method is an empty method. This is an extension point left by the spring container for developers.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

In this article, we explain in detail the process of parsing element objects into BeanDefinitionHolder objects in this source code.

Parse the element object and wrap it into a BeanDefinitionHolder object

Expand and explain the source code of BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele):

//Call here first, then use its overloaded method
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
   return parseBeanDefinitionElement(ele, null);
}

//This is the core method:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    //Get the id attribute
   String id = ele.getAttribute(ID_ATTRIBUTE);
   //Get the name attribute
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   
   //Get the alias. The name attribute can define multiple values (separated by comma/semicolon), and only the first value will be regarded as the value of name, and the remaining values will be treated as aliases.
   List<String> aliases = new ArrayList<>();
   if (StringUtils.hasLength(nameAttr)) {
      String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
      aliases.addAll(Arrays.asList(nameArr));
   }
    
   String beanName = id;
   //If there is no id attribute and the alias is not empty, then take the first value of the alias as the bean's id value.
   if (!StringUtils.hasText(beanName) & amp; & amp; !aliases.isEmpty()) {
      beanName = aliases.remove(0);
      if (logger.isTraceEnabled()) {
         logger.trace("No XML 'id' specified - using '" + beanName +
               "' as bean name and " + aliases + " as aliases");
      }
   }
   
   //containingBean is the overloaded method call of this method, so containingBean must be null.
   //The purpose of this code block is to check whether the beanName is unique.
   if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
   }
   
   //Here is the logic that actually encapsulates the beanDefinition. Here is the core code. Only the id and name tags have been solved above. There are many other tags, which are solved here.
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   
   if (beanDefinition != null) {
       //The following if block is executed when beanName does not exist. The reason for this situation is that there is neither an id attribute nor a name attribute in the <bean> tag. In this case, use your own default algorithm to help generate one
      if (!StringUtils.hasText(beanName)) {
         try {
             //This if block will definitely not be executed, and the else code block will be executed.
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            } else {
               //
               beanName = this.readerContext.generateBeanName(beanDefinition);
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null & amp; & amp;
                     beanName.startsWith(beanClassName) & amp; & amp; beanName.length() > beanClassName.length() & amp; & amp;
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
            if (logger.isTraceEnabled()) {
               logger.trace("Neither XML 'id' nor 'name' specified - " +
                     "using generated bean name [" + beanName + "]");
            }
         }
         catch (Exception ex) {
            error(ex.getMessage(), ele);
            return null;
         }
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      //Wrap the BeanDefinition object with one layer and return a BeanDefinitionHolder object.
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }

   return null;
}

Next, we will explain the source code of AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean):

public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {

   this.parseState.push(new BeanEntry(beanName));
    
   //Get class attribute
   String className = null;
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   //Get the parent attribute
   String parent = null;
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }
    
   try {
       //A basically empty BeanDefinition is created here. What is actually created is its implementation class GenericBeanDefinition.
       //Here we also set the name of its parent class and set the information of our own class.
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
       //Most tags are parsed here: singleton, scope, abstract, lazy-init, autowire, depends-on, primary, init-method, etc.
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      //Add some description text
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      //
      parseMetaElements(ele, bd);
      
      //The lookup-method tag is parsed here and the look-up method method is searched.
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      //The replaced-method tag is parsed here and replaced by the replace method method.
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      //Parse the constructor-arg tag here
      parseConstructorArgElements(ele, bd);
      //Parse the property tag here. What needs to be noted here is that spring encapsulates all property tags into the PropertyValues attribute in the BeanDefinition.
      parsePropertyElements(ele, bd);
      //Parse the qualifier tag here
      parseQualifierElements(ele, bd);
      //set source file xml here
      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));
      return bd;
   }
   catch (ClassNotFoundException ex) {
      error("Bean class [" + className + "] not found", ele, ex);
   }
   catch (NoClassDefFoundError err) {
      error("Class that bean class [" + className + "] depends on not found", ele, err);
   }
   catch (Throwable ex) {
      error("Unexpected failure during bean definition parsing", ele, ex);
   }
   finally {
      this.parseState.pop();
   }
   return null;
}

Register (storage) the BeanDefinitionHolder object

Next, we will explain the source code of BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()):

//This method has two parameters:
//definitionHolder: This is the BeanDefinition object that is wrapped in one layer
//registry: Registrar
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
   //Get the name of the bean
   String beanName = definitionHolder.getBeanName();
   //Store the BeanDefinition through the register, here is the core
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

   // Register aliases for bean name, if any.
   String[] aliases = definitionHolder.getAliases();
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}

Next, we will explain the registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()) source code:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {

   Assert.hasText(beanName, "Bean name must not be empty");
   Assert.notNull(beanDefinition, "BeanDefinition must not be null");
   //This if block is used for security verification. It is not important and can be skipped.
   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
      catch (BeanDefinitionValidationException ex) {
         throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
               "Validation of bean definition failed", ex);
      }
   }
   
   //Here is to determine whether this beanName has been registered.
   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   //If it is not empty, it has been registered. Then do some corresponding processing here, it’s not important
   if (existingDefinition != null) {
      if (!isAllowBeanDefinitionOverriding()) {
         throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
      }
      else if (existingDefinition.getRole() < beanDefinition.getRole()) {
         // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
         if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                  "' with a framework-generated bean definition: replacing [" +
                  existingDefinition + "] with [" + beanDefinition + "]");
         }
      }
      else if (!beanDefinition.equals(existingDefinition)) {
         if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                  "' with a different definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                  "' with an equivalent definition: replacing [" + existingDefinition +
                  "] with [" + beanDefinition + "]");
         }
      }
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
       //Here it is verified whether the bean of this factory has started the registration phase. Obviously it has not started yet, so the else block will be used, and the if block is useless.
      if (hasBeanCreationStarted()) {
         synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
         }
      }
      else {
         //Register/storage BeanDefinition, this is also the most critical
         this.beanDefinitionMap.put(beanName, beanDefinition);
         this.beanDefinitionNames.add(beanName);
         removeManualSingletonName(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }

   if (existingDefinition != null || containsSingleton(beanName)) {
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

Summarize:

This article explains the parsing, construction and registration of Spring’s default tags

In the next article we will explain the process of creating objects in Spring