[Dead Spring 15/43] --- Summary of IOC initialization for IOC

Posted by ltbaggz on Wed, 15 Dec 2021 22:46:49 +0100

[Spring 15/43] - Summary of IOC Initialization for IOC
https://www.cmsblogs.com/article/1391375372632854528

text

The first 13 Posts analyze the whole initialization process of IOC from the source level, and this one summarizes and concatenates these.

As mentioned earlier, the initialization process of an IOC container is divided into three steps: Resource location, BeanDefinition loading and parsing, and BeanDefinition registration.

  • Resource Location. Bean objects are usually described by external resources, so the first step in initializing an IOC container is to locate this external resource.
  • Loading and parsing of BeanDefinition. Loading is the loading of BeanDefinition. BeanDefinitionReader reads and parses Resource resources, which represent user-defined beans as the internal data structure of the IOC container: BeanDefinition. A BeanDefinition Map's data structure is maintained inside the IOC container, with one BeanDefinition object for each in the configuration file.
  • BeanDefinition registration. Register BeanDefinition with the IOC container as resolved in the second step, through the BeanDefinitionRegistery interface. Inside the IOC container is actually injecting BeanDefinitions parsed by the second process into a HashMap container through which the IOC container maintains these BeanDefinitions. One thing to note here is that this process does not complete dependency injection, and dependency registration occurs when the application first calls getBean() to ask the container for a bean. Of course, we can set preprocessing by setting the lazyinit property on a Bean so that the dependent injection of the Bean is done when the container is initialized.

Remember the code provided in the Loading Bean of the IOC Blog, which we started our study of IOC initialization as follows:

    ClassPathResource resource = new ClassPathResource("bean.xml");
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(resource);

You probably didn't know what the above lines of code meant at the beginning, but now it should be clear at a glance.

  • ClassPathResource = new ClassPathResource ("bean.xml");: Create a Resource resource object from an Xml configuration file. ClassPathResource is a subclass of the Resource interface, bean. The content in the XML file is the Bean information that we defined.
  • DefaultListableBeanFactory = new DefaultListableBeanFactory (); Create a BeanFactory. DefaultListableBeanFactory is a subclass of BeanFactory. As an interface, BeanFactory does not have its own functionality. DefaultListableBeanFactory is a truly stand-alone IOC container. It is the ancestor of the entire Spring IOC and will be analyzed in a special article.
  • XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);: Create an XmlBeanDefinitionReader reader to load BeanDefinition.
  • Reader. Load Bean Definitions (resources);: Open the loading and registration process for beans, and place the beans in the IOC container when finished.

Resource Location

In order to solve the problem of resource location, Spring provides two interfaces: Resource and ResourceLoader. The Resource interface is the abstract interface of Spring uniform resources, and the ResourceLoader is the uniform abstract of Spring resource loading. For more information on Resource and ResourceLoader, please pay attention to Spring --- IOC's Spring Unified Resource Loading Strategy

Locating a Resource resource requires two interfaces, Resource and ResourceLoader, to work together. In the code above, the new ClassPathResource("bean.xml") defines the resource for us. When did ResourceLoader initialize? Look at the XmlBeanDefinitionReader construction method:

        public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
            super(registry);
        }

Direct call to parent AbstractBeanDefinitionReader:

        protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
            Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
            this.registry = registry;
    
            // Determine ResourceLoader to use.
            if (this.registry instanceof ResourceLoader) {
                this.resourceLoader = (ResourceLoader) this.registry;
            }
            else {
                this.resourceLoader = new PathMatchingResourcePatternResolver();
            }
    
            // Inherit Environment if possible
            if (this.registry instanceof EnvironmentCapable) {
                this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
            }
            else {
                this.environment = new StandardEnvironment();
            }
        }

The core is to set the resourceLoader section, which is set if ResourceLoader is set, or PathMatchingResourcePatternResolver, which is a master ResourceLoader.

Loading and parsing BeanDefinition

Reader. LoadBeanDefinitions (resources); Opens the BeanDefinition parsing process. The following:

        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
            return loadBeanDefinitions(new EncodedResource(resource));
        }

This method wraps the resource resource resource into an EncodedResource instance object, then calls the loadBeanDefinitions() method, which encapsulates the resource into an EncodedResource mainly to encode the resource and ensure that the content is read correctly.

       public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
            // Omit some code
            try {
                // Convert resource file to IO stream of InputStream
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    // Parsing source of XML from InputStream
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
    
                    // Specific reading process
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }
                finally {
                    inputStream.close();
                }
            }
            // Omit some code
        }

Obtain the xml parsing source from the encodedResource source and call doLoadBeanDefinitions() to perform the specific parsing process.

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
            try {
                Document doc = doLoadDocument(inputSource, resource);
                return registerBeanDefinitions(doc, resource);
            }
            // Omit a lot of catch code

There are two main things to do in this method: 1, get the corresponding Document object from the xml parsing source, 2, call registerBeanDefinitions() to open the BeanDefinition parsing registration process.

Convert to Document Object

Calling doLoadDocument() converts a Bean-defined resource into a Document object.

        protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
            return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                    getValidationModeForResource(resource), isNamespaceAware());
        }

The loadDocument() method accepts five parameters:

  • inputSource: Source source to load Document
  • entityResolver: Parser for parsing files
  • errorHandler: Error handling the process of loading a Document object
  • validationMode: Validation mode
  • Namespace Aware: Namespace support. true if you want to provide support for XML namespaces

For these five parameters, there are two parameters to focus on: entityResolver, validationMode. These two parameters are described in detail in the Get Document Object for IOC and Get Verification Model for IOC.

loadDocument() provides an implementation in the class DefaultDocumentLoader as follows:

        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
          // Create File Resolution Factory
            DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
            if (logger.isDebugEnabled()) {
                logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
            }
            // Create a document parser
            DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
            // Resolve Spring's Bean Definition Resource
            return builder.parse(inputSource);
        }

Now that you have loaded and converted the defined Bean resource file into a Document object, the next step is to resolve it into a Spring IOC-managed Bean object and register it with the container. This process is implemented by the method registerBeanDefinitions(). The following:

        public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
           // Create BeanDefinitionDocumentReader to parse BeanDefinition in xml format
            BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
            // Get the number of beans registered in the container
            int countBefore = getRegistry().getBeanDefinitionCount();
    
            // Parsing process entry, where delegation mode is used, BeanDefinitionDocumentReader is just an interface.
            // The specific parsing implementation is accomplished by the implementation class DefaultBeanDefinitionDocumentReader
            documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
            return getRegistry().getBeanDefinitionCount() - countBefore;
        }

First create BeanDefinition's parser BeanDefinition DocumentReader, then call documentReader. RegiserBeanDefinitions () opens the parsing process using the delegation mode, which is accomplished by the subclass Default BeanDefinitionDocumentReader.

        public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
           // Get an XML descriptor
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
    
            // Get the root element of the Document
            Element root = doc.getDocumentElement();
    
            // Parse Root Element
            doRegisterBeanDefinitions(root);
        }

Resolution of Document Objects

Get the root element from the Document object and call doRegisterBeanDefinitions() to start the real parsing process.

            protected void doRegisterBeanDefinitions(Element root) {
                BeanDefinitionParserDelegate parent = this.delegate;
                this.delegate = createDelegate(getReaderContext(), root, parent);
        
               // Omit some code
        
                preProcessXml(root);
                parseBeanDefinitions(root, this.delegate);
                postProcessXml(root);
        
                this.delegate = parent;
            }
        

preProcessXml(), postProcessXml() are pre- and post-enhancements, and are currently empty implementations in Spring. parseBeanDefinitions() is a parsing and registration process for the root element root.

        protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
           // Bean-defined Document objects use Spring's default XML namespace
            if (delegate.isDefaultNamespace(root)) {
              // Gets all the child nodes of the root element of the Document object defined by the Bean
                NodeList nl = root.getChildNodes();
                for (int i = 0; i < nl.getLength(); i++) {
                    Node node = nl.item(i);
                    // Get Document Node Is XML Element Node
                    if (node instanceof Element) {
                        Element ele = (Element) node;
                        // Bean-defined Document element nodes use Spring's default XML namespace
                        if (delegate.isDefaultNamespace(ele)) {
                           // Resolve element nodes using Spring's Bean rules (default resolution rules)
                            parseDefaultElement(ele, delegate);
                        }
                        else {
                           // Element nodes are resolved using user-defined parsing rules instead of Spring's default XML namespace
                            delegate.parseCustomElement(ele);
                        }
                    }
                }
            }
            else {
              // The root node of the Document does not use Spring's default namespace and is resolved using user-defined parsing rules
                delegate.parseCustomElement(root);
            }
        }

Iterate over all the child nodes of the root element and judge them. If the node is the default namespace, ID calls parseDefaultElement() to open the parse registration process for the default label, otherwise parseCustomElement() is called to open the parse registration process for the custom label.

Label Resolution

If the element node defined uses the Spring default namespace, parseDefaultElement() is called for default tag resolution as follows:

        private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
           // If the element node is <Import>Import element, parse the import
            if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
                importBeanDefinitionResource(ele);
            }
            // If the element node is an <Alias>alias element, perform alias resolution
            else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
                processAliasRegistration(ele);
            }
            // If element node <Bean>element, register for Bean resolution
            else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
                processBeanDefinition(ele, delegate);
            }
    
            // //Beans parsing if element node <Beans>element
            else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
                // recurse
                doRegisterBeanDefinitions(ele);
            }
        }

Four tags are parsed: import, alias, beans, beans, in which the parsing of bean tags is the core work. The process of parsing each tag is described in the following article:

[Dead Strike Spring]----- IOC Analysis of Bean: analysis import Label
[Dead Strike Spring]—– IOC Analysis of bean Label: Open parsing process
[Dead Strike Spring]—– IOC Analysis of bean Label: BeanDefinition)
[Dead Strike Spring]—– IOC Analysis of bean Label: meta,lookup-method,replace-method)
[Dead Strike Spring]—– IOC Analysis of bean Label: constructor-arg,property Child Elements
[Dead Strike Spring]—– IOC Analysis of bean Tags: Resolve custom tags

parseCustomElement() is responsible for parsing the default tags.

        public BeanDefinition parseCustomElement(Element ele) {
            return parseCustomElement(ele, null);
        }
    
        public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
            String namespaceUri = getNamespaceURI(ele);
            if (namespaceUri == null) {
                return null;
            }
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler == null) {
                error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                return null;
            }
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }

Get the namespaceUri of the node, then get the corresponding Handler based on the namespaceuri, and call the parse() method of the Handler to complete the parsing and injection of the custom tag. For more information: [Stuff Spring]--- Resolve custom labels for IOC

Register BeanDefinition

With the above parsing, the Bean tags inside the Document object are resolved into BeanDefinitions one by one, and the next step is to register these BeanDefinitions into the IOC container. The action is triggered after parsing the Bean tag, as follows:

        protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
            if (bdHolder != null) {
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                try {
                    // 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);
                }
                // Send registration event.
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
        }

Call BeanDefinitionReaderUtils. RegiserBeanDefinition () registration, which actually calls RegiserBeanDefinition () of BeanDefinitionRegistry to register BeanDefinition, but the final implementation is in DefaultListableBeanFactory, as follows:

        @Override
        public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                throws BeanDefinitionStoreException {
    
          // Omit a bunch of checks
    
            BeanDefinition oldBeanDefinition;
    
            oldBeanDefinition = this.beanDefinitionMap.get(beanName);
              // Omit a bunch of if
                this.beanDefinitionMap.put(beanName, beanDefinition);
            }
            else {
                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);
                    this.beanDefinitionNames.add(beanName);
                    this.manualSingletonNames.remove(beanName);
                }
                this.frozenBeanDefinitionNames = null;
            }
    
            if (oldBeanDefinition != null || containsSingleton(beanName)) {
                resetBeanDefinition(beanName);
            }
        }

The core part of this code is this sentence. BeanDefinitionMap. Put (beanName, beanDefinition), so the registration process is not that big, it uses a collection object of Map to store, key is beanName, value is BeanDefinition.

At this point, the entire IOC initialization process has been completed, from the Bean resource location, to the Document object, then it is parsed, and finally registered in the IOC container, which has been perfectly completed. The IOC container now has configuration information for the entire beans that can be retrieved, used, maintained, which are the basis for controlling inversion and the dependency for subsequent injection of beans. Finally, a flowchart is used to end the summary.

Topics: Java Spring Container