3. The general process of Spring reading XML encapsulated BeanDefinition

Foreword:

The previous article introduced the Resource interface and introduced the core content of the Spring IOC factory.

This article will explain the general process of how Spring’s bottom layer reads XML through XmlBeanDefinitionReader and encapsulates it into a BeanDefinition.

Spring reads the process of XML encapsulated BeanDefinition

Call the XmlBeanFactory constructor.

Spring reads XML and encapsulates it into BeanDefinition with the following line of code:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

When creating the XmlBeanFactory factory, the constructor of the XmlBeanFactory class is called.

XmlBeanFactory source code:

public class XmlBeanFactory extends DefaultListableBeanFactory {
    
   private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
   
   //The above new XmlBeanFactory(resource object) will first call this constructor;
   public XmlBeanFactory(Resource resource) throws BeansException {
       //This method calls the overloaded method of this class internally.
      this(resource, null);
   }
   
    //This constructor will eventually be called.
   public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
       //Set the parent container
      super(parentBeanFactory);
      
      //Here is the core code, this.reader is the member variable of this class XmlBeanDefinitionReader
      //Read the resource by calling the loadBeanDefinitions method of XmlBeanDefinitionReader, that is, read the xml configuration file.
      this.reader.loadBeanDefinitions(resource);
   }
}

This class contains two overloaded constructors, and the second constructor will eventually be called.

The second constructor requires two parameters:

  1. resource: resource information
  2. parentBeanFactory: Parent factory. Spring allows multiple Spring factories to appear in one project at the same time. This situation is very rare. This situation has occurred in Springmvc, including the following two containers:
    1. Subcontainer created by DispatcherServlet: childFactory
    2. Subcontainer created by ContextLoaderListener: rootFactory

Therefore, spring usually takes into account the situation of the parent container during the design process, but in the process of developing with spring and reading the source code, we can basically ignore the situation of the parent container.

Let’s then parse the source code inside this.reader.loadBeanDefinitions(resource):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    //1. Call this method first
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //Because spring considers that resource files need to be encoded, so the resource is wrapped here.
        //But in our current default development process, coding is not required, so this coding operation has no effect. Here you can think of this EncodedResource as equivalent to resource.
       return loadBeanDefinitions(new EncodedResource(resource));
    }
    
    //2. Then call this, this is the real reading operation.
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        //This is just for printing a log, it’s not important.
       Assert.notNull(encodedResource, "EncodedResource must not be null");
       if (logger.isTraceEnabled()) {
          logger.trace("Loading XML bean definitions from " + encodedResource);
       }
        
        //Here we consider that there may be multiple configuration files, so we put them into a set collection here. unimportant
       Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
       if (!currentResources.add(encodedResource)) {
          throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
       }

        //The following part of the try block is the most core.
        //The resource object is first obtained in the parentheses of the try block, and then the InputStream file stream information is obtained.
        //Because we are passing ClassPathResource, and ClassPathResource indirectly implements the Resource interface, the Resource interface inherits the InputStreamSource interface, and the getInputStream() method is defined in the InputStreamSource interface.
        //So we can get the InputStream file stream information directly.
       try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
           //The core here is: parse the obtained inputStream input stream, and then encapsulate it into a parsed tool class InputSource
          InputSource inputSource = new InputSource(inputStream);
          //Because the default encoding of encodedResource is null, so if this if statement is false, the code inside will not be executed.
          if (encodedResource.getEncoding() != null) {
             inputSource.setEncoding(encodedResource.getEncoding());
          }
          
          /**
           * Here is the core of parsing, with two parameters:
           * inputSource: Tool class required for parsing xml parsing
           * encodedResource.getResource(): xml file that needs to be parsed
           * Through the following analysis, we can finally help us generate the final BeanDefinition
           */
          return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
          
          
       }catch (IOException ex) {
           throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
       } finally {
           currentResources.remove(encodedResource);
           if (currentResources.isEmpty()) {
              this.resourcesCurrentlyBeingLoaded.remove();
           }
       }
    }
}

Next let’s look at the source code in the doLoadBeanDefinitions method:

Some useless code is omitted here (exception handling, log output, etc.)

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {

   try {
      /*
      * Parse xml and encapsulate it into Document object.
      * Two parameters are required here:
      * 1.inputSource: Tool class for parsing xml files
      * 2.resource:xml file
      * Note: This Document object is for the object encapsulated after parsing xml. Document is not an object of spring, so spring does not need this object.
      * All later spring will convert this object into a BeanDefinition object again
      * */
      Document doc = doLoadDocument(inputSource, resource);

      /**
       * This method is to convert the document object into a BeanDefinition. This method is also the most important method.
       * Two parameters are required here:
       * 1.doc: document object encapsulated according to xml
       * 2.resource:xml file
       * spring converts the document object into a BeanDefinition object and returns the number of registrations.
       */
      int count = registerBeanDefinitions(doc, resource);
      return count;
   }
}

Then expand on the source code of the int count = registerBeanDefinitions(doc, resource) method:

//This is the core of registered BeanDefinition: Then let’s continue to find the core of this core code.
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
   BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
   //Before registering a new BeanDefinition, first count how many BeanDefinitions are currently registered by this register.
   int countBefore = getRegistry().getBeanDefinitionCount();
   //Here is the core: start registering BeanDefinition based on the document object here
   documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
   //Calculate the number of BeanDefinitions registered this time: get the number of BeanDefinitions in the registrar after registration, subtract the number of BeanDefinitions that already existed in the registrar before registration.
   return getRegistry().getBeanDefinitionCount() - countBefore;
}

Then expand on the source code of the documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) method:

//Call this method first
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   this.readerContext = readerContext;
   doRegisterBeanDefinitions(doc.getDocumentElement());
}


//Then call the internal method doRegisterBeanDefinitions of this class
protected void doRegisterBeanDefinitions(Element root) {
   
   BeanDefinitionParserDelegate parent = this.delegate;
   this.delegate = createDelegate(getReaderContext(), root, parent);
   
    //This piece of code is not very useful. Please read the comments in the if statement for the reason.
   if (this.delegate.isDefaultNamespace(root)) {
       //Here we start processing the tags in the xml file. root represents the root tag <beans> of the xml file.
       //root.getAttribute(PROFILE_ATTRIBUTE) This handles the profile attribute in <beans profile=""></beans>
       //profile means that spring can define the environment when writing xml, such as development environment (dev), production environment (production). You can then change the configuration of this attribute to achieve the purpose of switching environments.
       //This is currently not used much for development, so you don’t need to pay attention to it, so you don’t need to pay attention to the content in the if statement below.
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      //No need to pay attention to this if
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
               logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }

   preProcessXml(root);
   
   //Here is the real core method of registering BeanDefinition
   //root is the root tag <bean>, delegate is the corresponding register
   parseBeanDefinitions(root, this.delegate);
   
   postProcessXml(root);

   this.delegate = parent;
}

Then expand on the source code of the parseBeanDefinitions(root, this.delegate) method:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   if (delegate.isDefaultNamespace(root)) {
       //Get the root's child node list here, and then process it one by one in the for loop
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i + + ) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element element = (Element) node;
            
            //Here is the core
            //The final parsing method is the parseDefaultElement(ele, delegate) method in the if block and the delegate.parseCustomElement(ele) method in the else block
            if (delegate.isDefaultNamespace(ele)) {
                //The purpose of this method is: parse the basic tag, <bean id="" class="" parent="">
                //So here is the focus of analysis
               parseDefaultElement(ele, delegate);
            } else {
                //The purpose of this method is to parse custom tags
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

Basic tags:

<bean id="" class="" scope="" parent="" init-method="">
    <property name value
</bean>

<bean id="" class="" scope="" parent="" init-method="">
    <construt-arg>
 </bean>

Custom tags, new namespace tags:

<context:propertyplace-holder
<context:component-scan
..
<tx:annotation-driven
<mvc:annotation-drvent

<aop:config

Next, let’s talk about the source code of parseDefaultElement(ele, delegate) method for parsing basic tags:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //If it is an import tag: the function of the import tag is to introduce other xml configuration files into this xml
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   //If it is the alias tag: the alias tag is used to define the alias tag
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   //If it is a bean tag: here is the core focus of parsing into BeanDefinition
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
    //If it is beans tag
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      doRegisterBeanDefinitions(ele);
   }
}

Then expand on 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 monitors this event. If it is monitored, it will be processed later.
      //But this method is an empty method. This is an extension point left by the spring container for developers.
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

Summary:

After the above series of processes, everyone has basically understood how spring reads xml files, how to parse xml files, and finally saw the code that actually parses various tags.

This article ends here. In the next article, I will explain in detail how to parse element objects into BeanDefinitionHolder objects.