spring+dubbo3 container initialization-parsing xml principle

During the startup process of the spring container, the xml configuration file will be parsed, and the configuration file includes various tags, such as , , , , and other tags with different functions, then different tags have different functions. Therefore, for different tags, they must have a different set of parsing rules, that is, there must be a standard. What determines the parsing rules of your tags is the header information of xml.

<beans xmlns="http://www.springframework.org/schema/beans" <!-- Corresponds to <import>, <bean>, etc. -->
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop" <!-- Corresponding to the aop tag -->
       xmlns:context="http://www.springframework.org/schema/context" <!-- Corresponds to <context:component-scan>, <context:annotation-config>, etc.-->
       xmlns:task="http://www.springframework.org/schema/task" <!-- <task:annotation-driven> -->
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" <!-- Corresponds to <dubbo>-->
</beans>

Loading the configuration file is to convert the xml file into an input stream, then convert the input stream into a Document object, and then build an xml parser (such as defaultBeanDefinitionDocumentReader) to parse the Document.
Let’s go directly to the public method to see the execution process without going into details about the startup code.

AbstractBeanDefinitionReader:

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {<!-- -->
    //location is the name of our configuration file
    return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {<!-- -->
    ResourceLoader resourceLoader = getResourceLoader();
  
    if (resourceLoader instanceof ResourcePatternResolver) {<!-- -->
       // Resource pattern matching available.
       try {<!-- -->
          Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
          //Go in here again
          int count = loadBeanDefinitions(resources);
          if (actualResources != null) {<!-- -->
             Collections.addAll(actualResources, resources);
          }
          if (logger.isTraceEnabled()) {<!-- -->
             logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
          }
          return count;
       }
       catch (IOException ex) {<!-- -->
          throw new BeanDefinitionStoreException(
                "Could not resolve bean definition resource pattern [" + location + "]", ex);
       }
    }
    ..Most of the irrelevant code is omitted here..
}

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {<!-- -->
    Assert.notNull(resources, "Resource array must not be null");
    int count = 0;
    for (Resource resource : resources) {<!-- -->
        //This is the interface adjustment, the calling link is relatively long
       count + = loadBeanDefinitions(resource);
    }
    return count;
}

For the implementation of the interface, we take XmlBeanDefinitionReader as an example:

@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {<!-- -->
    return loadBeanDefinitions(new EncodedResource(resource));
}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {<!-- -->
    //By this time our xml file has become an input stream
    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {<!-- -->
       InputSource inputSource = new InputSource(inputStream);
       if (encodedResource.getEncoding() != null) {<!-- -->
          inputSource.setEncoding(encodedResource.getEncoding());
       }
       //You can go directly to this location to see
       return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
       throws BeanDefinitionStoreException {<!-- -->
    try {<!-- -->
        //Here, the file is converted into a document based on the xml input stream and resource.
       Document doc = doLoadDocument(inputSource, resource);
       int count = registerBeanDefinitions(doc, resource);
       if (logger.isDebugEnabled()) {<!-- -->
          logger.debug("Loaded " + count + " bean definitions from " + resource);
       }
       return count;
    }
}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {<!-- -->
    //Create a parser
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    //At this time, it will be parsed according to the parser.
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

The above is all preparation work, everything is ready, ready to start officially reading the configuration file information… Excited heart, trembling hands

DefaultBeanDefinitionDocumentReader:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {<!-- -->
    this.readerContext = readerContext;
    //go go go!!!
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

protected void doRegisterBeanDefinitions(Element root) {<!-- -->
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    
   ..Most of the irrelevant code is omitted here..
   
    preProcessXml(root);
    //Get dry
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

The following is a magical method, which is also in DefaultBeanDefinitionDocumentReader. Basically, when we read xml, we can imagine it as parsing the xml sequentially. When encountering any import tags, we recurse to this position to parse. This is a bit powerful.

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {<!-- -->
    //Here is to determine whether our xml is the default namespace
    //The default is http://www.springframework.org/schema/beans, which is the fixed beginning of our xml. This generally has no changes.
    if (delegate.isDefaultNamespace(root)) {<!-- -->
        //I understand this nl and the following nodes to be linked lists.
       NodeList nl = root.getChildNodes();
       for (int i = 0; i < nl.getLength(); i + + ) {<!-- -->
           //Parse each node into node
          Node node = nl.item(i);
          if (node instanceof Element) {<!-- -->
             Element element = (Element) node;
             //The ele we get here will contain namespaceURI, name, etc.,
             //namespaceURI is matched according to the tag in the header information we mentioned at the beginning
             //For example: name is a bean, which is the <bean> tag, and its namespaceURI is http://www.springframework.org/schema/beans, which is the default we mentioned above
             if (delegate.isDefaultNamespace(ele)) {<!-- -->
                parseDefaultElement(ele, delegate);
             }
             else {<!-- -->
                 //For example: name is context:component-scan, that is, <context:component-scan> tag,
                 //Its namespaceURI is http://www.springframework.org/schema/context
                delegate.parseCustomElement(ele);
             }
             
          }
       }
    }
    else {<!-- -->
       delegate.parseCustomElement(root);
    }
}

In this method of the default namespaceURI, we can clearly see what tags it supports.

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {<!-- -->
    //<import>
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {<!-- -->
        //When encountering the import tag, continue to call the loadBeanDefinitions method to parse the corresponding file.
       importBeanDefinitionResource(ele);
    }
    //<alias>
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {<!-- -->
       processAliasRegistration(ele);
    }
    //<beans>
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {<!-- -->
        //This is the logic of parsing bean tags, this is more important
       processBeanDefinition(ele, delegate);
    }
    //beans
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {<!-- -->
       // recurse
       doRegisterBeanDefinitions(ele);
    }
}

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {<!-- -->
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {<!-- -->
        //Here is to create a BeanDefinitionHolder based on ele, that is, put the information in the bean tag in xml, such as attribute values, etc. into the BeanDefinitionHolder object
       bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
       try {<!-- -->
          // Here we put the BeanDefinitionHolder into the container, but the subsequent conversion will be a BeanDefinition object. At this point we know the origin of beanDefinitionMap
          BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
       }
       catch (BeanDefinitionStoreException ex) {<!-- -->
          getReaderContext().error("Failed to register bean definition with name '" +
                bdHolder.getBeanName() + "'", ele, ex);
       }
       // Send registration event.
       getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

When the parseBeanDefinitions method above is not in the default namespace, use the delegate.parseCustomElement(ele) method. It is also very important. Only by understanding this method can we know what to do when we customize the parser. Next we go to BeanDefinitionParserDelegate. Let’s see the specific implementation of this method

BeanDefinitionParserDelegate:

@Nullable
public BeanDefinition parseCustomElement(Element ele) {<!-- -->
    return parseCustomElement(ele, null);
}

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {<!-- -->
    //This is the same as what we mentioned before, which is to get the specified tag and what is the corresponding Namespace?
    // For example, for the context:component-scan tag, its namespaceUri is http://www.springframework.org/schema/context
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {<!-- -->
       return null;
    }
    //Resolve here is another very detailed method. We need to parse namespaceUri because we know the namespace corresponding to the specified tag. Then I also need to know what the corresponding parsing class is. This is what is done here. ,
    //Can be understood as a strategy pattern
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {<!-- -->
       error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
       return null;
    }
    //Here is the implementation of the corresponding handler. Some public ones will use the parse in NamespaceHandlerSupport.
    //Some special ones, such as DubboNamespaceHandler, will be defined by themselves, and then corresponding to different BeanDefinitionParser implementations, such as DubboBeanDefinitionParser, will be parsed in their own implementation and put the beanDefinition in the container.
    //You can check this based on your actual situation
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

Go to DefaultNamespaceHandlerResolver to see its resolve method

public NamespaceHandler resolve(String namespaceUri) {<!-- -->
    //Here are the files whose class path is META-INF/spring.handlers in the project. Here are the key points.
    //From this we know that when we want to customize a namespace and parsing class, we also need to create this file. The parsed handlerMappings are in the same format as the following, and it suddenly dawns on us.
    //{<!-- -->
    //"http://dubbo.apache.org/schema/dubbo": "org.apache.dubbo.config.spring.schema.DubboNamespaceHandler",
    //"http://www.springframework.org/schema/aop": "org.springframework.aop.config.AopNamespaceHandler",
    //"http://www.springframework.org/schema/task": "org.springframework.scheduling.config.TaskNamespaceHandler",
    //"http://mybatis.org/schema/mybatis-spring": "org.mybatis.spring.config.NamespaceHandler"
    //}
    Map<String, Object> handlerMappings = getHandlerMappings();
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {<!-- -->
       return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {<!-- -->
       return (NamespaceHandler) handlerOrClassName;
    }
    else {<!-- -->
       String className = (String) handlerOrClassName;
       try {<!-- -->
          Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
          if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {<!-- -->
             throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                   "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
          }
          NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
          namespaceHandler.init();
          handlerMappings.put(namespaceUri, namespaceHandler);
          return namespaceHandler;
       }
       catch (ClassNotFoundException ex) {<!-- -->
          throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                "] for namespace [" + namespaceUri + "]", ex);
       }
       catch (LinkageError err) {<!-- -->
          throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                className + "] for namespace [" + namespaceUri + "]", err);
       }
    }
}

After looking at the whole process, we can parse the xml in the project, and there will be something in the beanDefinitionMap of the beanFactory. The subsequent bean instantiation will be initialized based on the BeanDefinition we put in.