Spring source code reading - 1-xml load parsing.md

XML load parsing

1. Load Bean definition

1.1 parsing XML documents

XmlBeanDefinitionReader#loadBeanDefinitions

/**
 * Load xml resources, parse all xml tags, encapsulate the bean corresponding to xml as beandefinition (the real type is GenericBeanDefinition), and register
 * Returns the number of loaded beandefinitions
 */
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
   // EncodedResource adds encoding and charset attributes
   Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
   if (currentResources == null) {
      currentResources = new HashSet<>(4);
      this.resourcesCurrentlyBeingLoaded.set(currentResources);
   }
    // Record the currently loaded xml. Loading
   if (!currentResources.add(encodedResource)) {
      throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
   }
      // Get the configuration resource input file stream object (xml file input stream)
      InputStream inputStream = encodedResource.getResource().getInputStream();
      // org.xml.sax.InputSource
      // Encapsulate InputSource to prepare for xml parsing
      InputSource inputSource = new InputSource(inputStream);
      if (encodedResource.getEncoding() != null) {
          inputSource.setEncoding(encodedResource.getEncoding());
      }
      // Real processing
      return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
	....
   finally {
      currentResources.remove(encodedResource);
      if (currentResources.isEmpty()) {
          // Load end delete
         this.resourcesCurrentlyBeingLoaded.remove();
      }
   
}

This method is the top-level method for loading xml. First, it is connected to the XML configuration file, then the file is encapsulated as InputSource, and then the real processing of XML is handed over to the doloadbean definitions method.

XmlBeanDefinitionReader#doLoadBeanDefinitions

/**
 * Further split the task
 * Parsing and encapsulating xml files into documents
 * Then register the bean information according to the Document
 * @param inputSource
 * @param resource
 * @return
 * @throws BeanDefinitionStoreException
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
      throws BeanDefinitionStoreException {
   try {
      // Get the validation schema for the xml file and load the xml file as document
      Document doc = doLoadDocument(inputSource, resource);
      // Register Bean information according to the returned Document
      return registerBeanDefinitions(doc, resource);
   }
  ...
}

This method further splits the task. DefaultDocumentLoader#loadDocument parses the xml stream into a Document object. XmlBeanDefinitionReader#registerBeanDefinitions further parses and registers bean information according to the Document object.

XmlBeanDefinitionReader#doLoadDocument

/**
 * Parsing XML 
 * Allows you to include XML fragments into application context definitions using standard XML entities, such as splitting large XML files into modules
 */
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
   // getValidationModeForResource gets the validation mode (1, DTD, 2, xsd) of the XML file. If it is not set, the stream automatic judgment mode will be loaded
   // getEntityResolver XML parser
   return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
         getValidationModeForResource(resource), isNamespaceAware());
}

XmlBeanDefinitionReader#registerBeanDefinitions registers each bean definition in the given root element through DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions.

protected void doRegisterBeanDefinitions(Element root) {
   ....
   if (this.delegate.isDefaultNamespace(root)) {
      // Multiple environments can be configured by handling the profile property of beans
      String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
      if (StringUtils.hasText(profileSpec)) {
         String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
               profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
         if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isInfoEnabled()) {
               logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                     "] not matching: " + getReaderContext().getResource());
            }
            return;
         }
      }
   }
   // Preprocessing is left to subclasses
   preProcessXml(root);
   // Real parsing bean definitions
   parseBeanDefinitions(root, this.delegate);
   // Post processing is left to subclasses
   postProcessXml(root);
   ....
}

DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   // Processing beans
   if (delegate.isDefaultNamespace(root)) {
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            // Processing bean s
            Element ele = (Element) node;
            if (delegate.isDefaultNamespace(ele)) {
               // Process the default http //w ww.springframework org/scherna beans
               parseDefaultElement(ele, delegate);
            }
            else {
                // Handle customized ones, such as those under tx
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      delegate.parseCustomElement(root);
   }
}

At this point, start to cycle and parse the sub tags according to the root (beans) node. The sub tags are divided into default tags and user-defined tags. These two processing methods are very different. The following are all about the default label, and the user-defined label is described separately.

Tasks completed here:

  1. The xml resource resource is loaded and encapsulated as encoded resource (charset can be set).
  2. Encapsulate the XML resource input stream object as an InputSource for the DocumentLoader to load and parse into a Document object (you need to specify an XML parser and which XML format is the standard mode (xsd|dtd)).
  3. Get the root node through the Document to start parsing. Before that, it can be processed by the pre processor (root), and after that, it can be processed by the post processor (root).
  4. Loop parsing all sub tags under root. The tags are divided into default tags and user-defined tags. The processing is very different. Default tags, such as bean and import, and custom tags, such as tx.

1.2 parsing default labels

DefaultBeanDefinitionDocumentReader#parseDefaultElement

/**
 * beans.xsd By default, there are four types of import beans beans alias
 * @param ele
 * @param delegate
 */
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   // import type processing
   // Yes, plus is to recursively call the bean method of loading the configuration file definition
   // XMLBeanDefinitionReader.loadBeanDefinitions(resource)
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   // alias processing
   // The processing is the same as that of the name attribute in the bean
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);
   }
   // bean processing
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);
   }
   // beans processing
   // Nesting without check-in is no big difference. Call the beans parsing process in a loop
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}

This method handles each default tag differently. In fact, both import and beans call the parsing process recursively, so it mainly depends on alias and bean.

bean tag parsing

DefaultBeanDefinitionDocumentReader#processBeanDefinition

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // Resolve various default properties under the bean and store them in the bdholder object
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
   if (bdHolder != null) {
      // If bdholder is not empty, check whether there are custom attributes and resolve them
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      // After parsing, bdHolder will have all the information of the bean tag
      try {
         // Then register in the delegate BeanDefinitionReaderUtils.registerBeanDefinition
         // Register the final decorated instance.
         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
      }
      catch (BeanDefinitionStoreException ex) {
         getReaderContext().error("Failed to register bean definition with name '" +
               bdHolder.getBeanName() + "'", ele, ex);
      }
      // Notify this bean that registration is complete
      // For future expansion, it is not implemented at present
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
   }
}

In this method, BeanDefinitionParserDelegate.parseBeanDefinitionElement parses the default tag and saves the parsed information to BeanDefinitionHolder

BeanDefinitionParserDelegate#parseBeanDefinitionElement

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
   // Parse id
   String id = ele.getAttribute(ID_ATTRIBUTE);
   // Resolve name
   String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
   // Split name
   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;
   // id is null, use the first of name
   if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
      beanName = aliases.remove(0);
   	  ...
   }
   // Check the uniqueness of beanName in the bean container
   if (containingBean == null) {
      checkNameUniqueness(beanName, aliases, ele);
   }
   // Further parsing all other attributes of a bean is to parse all attributes and elements in a bean into JavaBeans, that is, AbstractBeanDefinition
   // There are AbstractBeanDefinition classes for beans in xml
   AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
   if (beanDefinition != null) {
      if (!StringUtils.hasText(beanName)) {
           ...
            // beanname does not exist. The beanname is generated for the current bean based on the naming rules provided by Spring
            if (containingBean != null) {
               beanName = BeanDefinitionReaderUtils.generateBeanName(
                     beanDefinition, this.readerContext.getRegistry(), true);
            }
            else {
               beanName = this.readerContext.generateBeanName(beanDefinition);
               String beanClassName = beanDefinition.getBeanClassName();
               if (beanClassName != null &&
                     beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                     !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                  aliases.add(beanClassName);
               }
            }
           ...
      }
      String[] aliasesArray = StringUtils.toStringArray(aliases);
      return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
   }
   return null;
}

In this method, the attributes of the bean tag (only id and name are resolved, and others are resolved in the following methods). The resolution of the sub tag is handed over to the lower level for processing (BeanDefinitionParserDelegate#parseBeanDefinitionElement), and the resolution of the custom tag under the bean is processed separately.

BeanDefinitionParserDelegate#parseBeanDefinitionElement

/**
 * Parse the bean definition itself, without regard to name or aliases. May return
 * {@code null} if problems occurred during the parsing of the bean definition.
 */
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
      Element ele, String beanName, @Nullable BeanDefinition containingBean) {
   // A collection (LinkedList) will the beanname record being parsed
   this.parseState.push(new BeanEntry(beanName));
   // Parse class
   String className = null;
   if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
      className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
   }
   // Parse parent
   String parent = null;
   if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
      parent = ele.getAttribute(PARENT_ATTRIBUTE);
   }
	...
      // Create a GenericBeanDefinition to host the property AbstractBeanDefinition type
      // BeanDefinition is the internal representation of the configuration file < bean > element label in the container
      // The < bean > element tag has
      //Class scope lazy init and other configuration attributes. BeanDefinition provides the corresponding beanclass scope lazy init
      //Attribute. The attribute in beandefinition < bean > is a corresponding attribute
      // Spring converts the < bea > configuration information in the configuration file into the internal representation of the container through BeanDefinition
      //These beandefinitions are registered in beandefinitionregistry. Beandefinitionregistry for Spring container γ  Like
      //The memory database of Spring configuration information is mainly saved in the form of map. Subsequent operations directly read the configuration information from the BeanDefinition Registry
      AbstractBeanDefinition bd = createBeanDefinition(className, parent);
      // Hard coded parsing of various default properties of the default bean
      parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
      // The following is all the analysis of child elements
      // Extract description
      bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
      // Parsing meta metadata child elements
      // Take out all meta sub elements, encapsulate the key value into a BeanMetadataAttribute object (including) and save it to beanmetadatattributeaccessor
      // AbstractBeanDefinition integrates BeanMetadataAttributeAccessor
      parseMetaElements(ele, bd);
      // Parsing lookup method
      // The abstract method returns the specified bean < lookup method name = "getBean" bean = "bean" > getBean is an abstract method
      parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
      // Resolve replaced method
      // The difference between the lookup method and the lookup method is that the lookup method customizes the returned bean s, and the replaced method can replace the logic of the method
      // Will be encapsulated as methodOverrides and stored in the methodOverrides of bd
      parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
      // Resolve constructor parameters
      parseConstructorArgElements(ele, bd);
      // Resolving property child elements
      parsePropertyElements(ele, bd);
      // Resolve the qualifier child element to specify beanName disambiguation
      parseQualifierElements(ele, bd);
      bd.setResource(this.readerContext.getResource());
      bd.setSource(extractSource(ele));

      return bd;
	...
   return null;
}

This method first parses the class and parent of the bean tag, then creates a default AbstractBeanDefinition, then parses various default attributes of the bean through BeanDefinitionParserDelegate#parseBeanDefinitionAttributes, and then parses other sub tags (such as lookup method, property, constructor, etc.) in this method.

BeanDefinitionParserDelegate#parseBeanDefinitionAttributes

/**
 * Applies the attributes of a given bean element to a given bean definition
 * Apply the attributes of the given bean element to the given bean * definition.
 * @param ele bean declaration element
 * @param beanName bean name
 * @param containingBean containing bean definition
 * @return a bean definition initialized according to the bean element attributes
 */
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
      @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {

   // Parsing singleton using singleton will prompt an error
   if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
      error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
   }
   // Parse scope
   else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
      bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
   }
   // When beanDifinition is embedded and the scope attribute is not specified separately, the default attribute of the parent class is used
   else if (containingBean != null) {
      // Take default from containing bean in case of an inner bean definition.
      bd.setScope(containingBean.getScope());
   }
   // Parse abstract
   if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
      bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
   }
   // Parsing lazy init
   String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
   if (isDefaultValue(lazyInit)) {
      lazyInit = this.defaults.getLazyInit();
   }
   // Non true strings or empty strings will be false
   bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
   // Parsing autowire
   String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
   bd.setAutowireMode(getAutowireMode(autowire));
   // Parsing dependencies on
   if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
      String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
      bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
   }
   // Resolve autowire candidate attribute
   String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
   if (isDefaultValue(autowireCandidate)) {
      String candidatePattern = this.defaults.getAutowireCandidates();
      if (candidatePattern != null) {
         String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
         bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
      }
   }
   else {
      bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
   }
   // Resolve primary attribute
   if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
      bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
   }
   // Parsing init method
   if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
      String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
      bd.setInitMethodName(initMethodName);
   }
   else if (this.defaults.getInitMethod() != null) {
      bd.setInitMethodName(this.defaults.getInitMethod());
      bd.setEnforceInitMethod(false);
   }
   // Parse destroy method
   if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
      String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
      bd.setDestroyMethodName(destroyMethodName);
   }
   else if (this.defaults.getDestroyMethod() != null) {
      bd.setDestroyMethodName(this.defaults.getDestroyMethod());
      bd.setEnforceDestroyMethod(false);
   }
   // Parse factory method
   if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
      bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
   }
   // Parse factory bean
   if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
      bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
   }
   return bd;
}

So far, the default label parsing of a bean is completed, and all the information of the bean label in xml is encapsulated in the AbstractBeanDefinition object.

The parsing of custom tags under bean s will be described separately

After parsing the default tag and custom tag of the bean in the DefaultBeanDefinitionDocumentReader#processBeanDefinition, you need to register the object (BeanDefinitionHolder) that holds the bean definition information into the Spring container through the DefaultListableBeanFactory#registerBeanDefinition method.

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
      throws BeanDefinitionStoreException {
    ...
   if (beanDefinition instanceof AbstractBeanDefinition) {
      try {
         //  The last verification before registration. The verification here is different from the previous XML file verification,
         //  It is mainly used to verify methodOverrides in AbstractBeanDefinition
         //  Check whether methodOverrides coexist with factory methods or the method corresponding to methodOverrides does not exist at all
         ((AbstractBeanDefinition) beanDefinition).validate();
      }
    ...
   }
   // Thread safe map, so there is no need to use synchronized here
   BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
   // The bean queried by beanName already exists
   if (existingDefinition != null) {
      // A series of judgments
      // Allow Bean definition overrides check whether overrides are allowed
      // If the corresponding BeanName has been registered and the bean configured in the configuration is not allowed to be overwritten, an exception is thrown
      // Is the current object to be saved the same as the one to be taken out
      // Register beanDefinition
      this.beanDefinitionMap.put(beanName, beanDefinition);
   }
   else {
       // Is the bean marked as created
       // Full replication required
      if (hasBeanCreationStarted()) {
         // Cannot modify startup-time collection elements anymore (for stable iteration)
         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;
            if (this.manualSingletonNames.contains(beanName)) {
               Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
               updatedSingletons.remove(beanName);
               this.manualSingletonNames = updatedSingletons;
            }
         }
      }
      else {
         // Still in startup registration phase
         this.beanDefinitionMap.put(beanName, beanDefinition);
         // Record bean name
         this.beanDefinitionNames.add(beanName);
         this.manualSingletonNames.remove(beanName);
      }
      this.frozenBeanDefinitionNames = null;
   }
	// The bean is overridden by a new definition or is a singleton
   if (existingDefinition != null || containsSingleton(beanName)) {
      // Resets all bean definition caches for a given bean, including those derived from it
      resetBeanDefinition(beanName);
   }
   else if (isConfigurationFrozen()) {
      clearByTypeCache();
   }
}

At this point, the bean parsing and registration are all completed

In addition to resolving several specific attributes, import is a recursive call to the beans parsing process.

The case where beans are included under beans is also the parsing process of recursively calling beans.

Alias mainly resolves the default attribute and records the association between alias and beanname.

Completed tasks:

  1. Resolve different sub tags under root (import beans alias)

  2. Resolve all default attributes (class, id, etc.) of the bean tag

  3. Resolve all sub tags of the bean tag (property, etc.)

  4. Resolve all custom tags under the bean

  5. Register the AbstractBeanDefinition object that carries all the information of the bean tag in the xml into the Spring container (in fact, it is a map, which is saved in it)

    Note: the resolution of custom tags is not described separately.

                                                                                                                       ---- **Cover <Spring In depth analysis of source code**

Tags: Java Spring source code

Posted on Thu, 11 Nov 2021 05:22:42 -0500 by academy.