Popular understanding of spring source code -- Analysis of default tags (import, alias, beans)

Posted by shahryar on Sun, 07 Jun 2020 06:03:10 +0200

Popular understanding of spring source code (6) -- Analysis of default tags (import, alias, beans)

The parseDefaultElement method of documentReader is mentioned in the previous section. From this point on, various labels are parsed. Among them, bean label parsing is the most complex. So let's first look at the other three default labels

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        //analysis import label
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        //analysis alias label
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        //analysis bean label
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        //analysis beans label
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

1. Analysis of import label

Enter the importBeanDefinitionResource method

    protected void importBeanDefinitionResource(Element ele) {
        //obtain import Tagged resource Property value
        String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        if (!StringUtils.hasText(location)) {
            getReaderContext().error("Resource location must not be empty", ele);
            return;
        }

        //Yes location Replace placeholders in
        // Resolve system properties: e.g. "${user.dir}"
        location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

        Set<Resource> actualResources = new LinkedHashSet<>(4);

        //Judge absolute path or relative path
        // Discover whether the location is an absolute or relative URI
        boolean absoluteLocation = false;
        try {
            absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
        }
        catch (URISyntaxException ex) {
            // cannot convert to an URI, considering the location relative
            // unless it is the well-known Spring prefix "classpath*:"
        }

        // Absolute or relative?
        if (absoluteLocation) {
            try {
                //If it's an absolute path, here's readerContext.getReader()What you get is the beginning XmlBeanDefinitionReader,
                //Equivalent to recursive call loadBeanDefinitions Method according to location Load new profile
                int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
                if (logger.isTraceEnabled()) {
                    logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
                }
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error(
                        "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
            }
        }
        else {
            // No URL -> considering resource location as relative to the current file.
            try {
                int importCount;
                //If it is a relative path, the current Resource Analyze the relative Resource
                Resource relativeResource = getReaderContext().getResource().createRelative(location);
                //Is this a call or a call loadBeanDefinitions Method, just another method overload
                if (relativeResource.exists()) {
                    importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                    actualResources.add(relativeResource);
                }
                else {
                    String baseLocation = getReaderContext().getResource().getURL().toString();
                    importCount = getReaderContext().getReader().loadBeanDefinitions(
                            StringUtils.applyRelativePath(baseLocation, location), actualResources);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
                }
            }
            catch (IOException ex) {
                getReaderContext().error("Failed to resolve current resource location", ele, ex);
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error(
                        "Failed to import bean definitions from relative location [" + location + "]", ele, ex);
            }
        }
        Resource[] actResArray = actualResources.toArray(new Resource[0]);
        //After parsing, activate the listener
        getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
    }

It can be found that the processing of the import tag here is mainly to process its resource attribute, whether it is a relative path or an absolute path, and finally get the corresponding resource resource, call the first loadBeanDefinitions method, just call different overloaded methods of loadBeanDefinitions, and the core processing is the same, which is equivalent to a recursive call.

Finally, the handling of the event is initiated, which will be explained later.

2. Analysis of alias tag

spring's alias tag should be used less, even in the later notes, it's not used much, let's talk about it here, the focus is to learn its design ideas.

    protected void processAliasRegistration(Element ele) {
        //analysis name attribute
        String name = ele.getAttribute(NAME_ATTRIBUTE);
        //analysis alias attribute
        String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
        //Default verification succeeded
        boolean valid = true;
        //name Non empty judgment
        if (!StringUtils.hasText(name)) {
            getReaderContext().error("Name must not be empty", ele);
            valid = false;
        }
        //alias Empty judgment
        if (!StringUtils.hasText(alias)) {
            getReaderContext().error("Alias must not be empty", ele);
            valid = false;
        }
        if (valid) {
            try {
                //If the verification is successful, start alias Registration of
                getReaderContext().getRegistry().registerAlias(name, alias);
            }
            catch (Exception ex) {
                getReaderContext().error("Failed to register alias '" + alias +
                        "' for bean with name '" + name + "'", ele, ex);
            }
            //Initiating an event
            getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
        }
    }

The previous logic is relatively simple, focusing on the registration of aliases. getReaderContext().getRegistry(), which is to get the registration center from ReaderContext. The Registry here is the first DefaultListableBeanFactory.

If you see this in the first article of this series, you should understand that the ReaderContext context is always throughout, and it refers to XmlBeanDefinitionReader object and Resource object.

The DefaultListableBeanFactory is not only a beandefinition registry, but also an alias registry, which inherits from the simpleliasregistry.

Access SimpleAliasRegistry.registerAlias(name, alias).

    public void registerAlias(String name, String alias) {
        Assert.hasText(name, "'name' must not be empty");
        Assert.hasText(alias, "'alias' must not be empty");
        //there map,with alias by key,name by value,Save alias and name Mapping of
        synchronized (this.aliasMap) {
            //If name and alias Equal, remove the alias and name Mapping of
            if (alias.equals(name)) {
                this.aliasMap.remove(alias);
                if (logger.isDebugEnabled()) {
                    logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
                }
            }
            else {
                //according to alias Get registered name
                String registeredName = this.aliasMap.get(alias);
                if (registeredName != null) {
                    //If name Already exists, so you don't need to register
                    if (registeredName.equals(name)) {
                        // An existing alias - no need to re-register
                        return;
                    }
                    //Judge whether to allow aliases to be overwritten. It is allowed by default
                    if (!allowAliasOverriding()) {
                        throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                                name + "': It is already registered for name '" + registeredName + "'.");
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                                registeredName + "' with new target name '" + name + "'");
                    }
                }
                //Check circular references
                checkForAliasCircle(name, alias);
                //Direct to map Middle release
                this.aliasMap.put(alias, name);
                if (logger.isTraceEnabled()) {
                    logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
                }
            }
        }
    }

The registration of aliases is not very complicated. After various checks, the mapping between aliases and name s is saved in the map, and the circular reference of checking aliases is a little more complicated

    protected void checkForAliasCircle(String name, String alias) {
        if (hasAlias(alias, name)) {
            throw new IllegalStateException("Cannot register alias '" + alias +
                    "' for name '" + name + "': Circular reference - '" +
                    name + "' is a direct or indirect alias for '" + alias + "' already");
        }
    }
    public boolean hasAlias(String name, String alias) {
        for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
            String registeredName = entry.getValue();
            //Judge what you want to register alias Did you do anything name Registered
            if (registeredName.equals(name)) {
                //If so, judge the registered name Corresponding alias Do you want to register with name identical
                String registeredAlias = entry.getKey();
                //If not, recursively calls the current method
                if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
                    return true;
                }
            }
        }
        return false;
    }

Recursively call hasAlias method to determine whether the alias has been registered as name, and whether the registered alias is equal to the name you want to register. Note that here the parameter name is passed to the parameter alias, and the parameter alias is passed to the parameter name. This is not intended.

It looks a little winding. for instance.

        SimpleAliasRegistry registry = new SimpleAliasRegistry();
        registry.registerAlias("a","b");
        registry.registerAlias("b","a");

Running this code will throw this exception

Cannot register alias 'a' for name 'b': Circular reference - 'b' is a direct or indirect alias for 'a' already

Such a case where b is the alias of a and a is the alias of b is called a circular reference of the alias. spring doesn't allow this because it doesn't make sense.

In this case, it is a direct circular reference, so the error message, more specifically, is as follows:

'b' is a direct alias for 'a' already

And here's what happens

        registry.registerAlias("a","b");
        registry.registerAlias("b","c");
        registry.registerAlias("c","a");

An error will be reported when running the third section of code, which belongs to indirect circular reference of alias. Although c is not directly registered as an alias of a, after the first two sections of code, c is already an alias of a indirectly.

This code will call hasAlias method recursively, so the error message should be clear

'c' is a indirect alias for 'a' already

3. Analysis of embedded beans label

        //analysis beans label
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse recursion
            doRegisterBeanDefinitions(ele);
        }

The embedded beans tags may not be used much, but we often use import tags to import another xml file in the xml configuration file. In fact, we also import a beans tag, so the effect is similar.

It's similar to parsing the import tag. Because another xml needs XSD verification and other operations, it starts from the loadBeanDefinitions, and parsing the embedded beans tag is very simple. It's OK to call the first doRegisterBeanDefinitions method recursively

Post the previous code here again.

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        //Entrusted to delegate analysis
        this.delegate = createDelegate(getReaderContext(), root, parent);

        //Judge the current Beans Is node the default namespace
        if (this.delegate.isDefaultNamespace(root)) {
            //obtain beans Node's profile attribute
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                //You can use commas or semicolons to beans Label specified as multiple profile type
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // We cannot use Profiles.of(...) since profile expressions are not supported
                // in XML config. See SPR-12458 for details.
                //Judge current beans Tagged profile Is it activated
                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;
                }
            }
        }
        //Parsing preprocessing, left to subclass implementation
        preProcessXml(root);
        //Real parsing process
        parseBeanDefinitions(root, this.delegate);
        //After parsing, leave it to subclass implementation
        postProcessXml(root);

        this.delegate = parent;
    }

This is the end of the analysis of import, alias and beans tags. The analysis of bean tags is a bit complicated. The analysis of import and beans tags will eventually enter the analysis of bean tags, which will be described in detail in the next chapter.

Go too far, don't forget why!

 

Reference: deep analysis of spring source code

Topics: Java Spring xml Attribute less