8. [kowtow to Spring] - IoC's parsing Bean: parsing the import tag

Keywords: Spring xml Java encoding

In blogs [kowtow to Spring] - IoC's registration BeanDefinitions According to the analysis in, there are two ways to parse beans in Spring:

  • If the root node or child node adopts the default namespace, the ා parseDefaultElement(...) method is called for default label resolution
  • Otherwise, the beandefinitionparserdelegate ා parsecustomelement (...) method is called for custom parsing.

Therefore, the following blog analyzes and explains these two methods in detail. In this article, we start with the default label parsing process. The code is as follows:

// DefaultBeanDefinitionDocumentReader.java

public static final String IMPORT_ELEMENT = "import";
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String NESTED_BEANS_ELEMENT = "beans";

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

The function of this method is clear at a glance. It analyzes four different Tags: import, alias, bean and beans. We start with the first tag, import.

1. import example

Small partners who have experienced the Spring configuration file know that if the project is relatively large, the maintenance of the configuration file will make people feel terrible. There are too many files. Imagine putting all configurations in a spring.xml configuration file. Is that obvious?

Spring provides a sub module idea for this situation. Using the import tag, for example, we can construct such a spring.xml.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="spring-student.xml"/>

    <import resource="spring-student-dtd.xml"/>

</beans>

In the spring.xml configuration file, use the import tag to import the configuration files of other modules.

  • If the configuration needs to be modified, directly modify the corresponding configuration file.
  • If a new module needs to be introduced, add import directly.

This greatly simplifies the complexity of post configuration maintenance, but also easy to manage.

2. importBeanDefinitionResource

Spring uses the #importBeanDefinitionResource(Element ele) method to complete the parsing of the import tag.

// DefaultBeanDefinitionDocumentReader.java

/**
 * Parse an "import" element and load the bean definitions
 * from the given resource into the bean factory.
 */
protected void importBeanDefinitionResource(Element ele) {
    // <1> Get the property value of resource
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    // Empty, exit directly
    if (!StringUtils.hasText(location)) {
        getReaderContext().error("Resource location must not be empty", ele); // Error reporting using problemReporter
        return;
    }

    // <2> Resolve system properties in the format of ${user.dir}
    // Resolve system properties: e.g. "${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    // The actual Resource collection, that is, the address of the import, and what Resource resources are there
    Set<Resource> actualResources = new LinkedHashSet<>(4);

    // <3> Determine whether the location is a relative path or an absolute 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?
    // <4> Absolute path
    if (absoluteLocation) {
        try {
            // Add the Resource of the profile address to the actual resources, and load the corresponding bean definitions
            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);
        }
    // <5> Relative path
    } else {
        // No URL -> considering resource location as relative to the current file.
        try {
            int importCount;
            // Create Resource for relative address
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            // existence
            if (relativeResource.exists()) {
                // Load beandefinitions in relativeResource
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                // Add to actualResources
                actualResources.add(relativeResource);
            // Non-existent
            } else {
                // Get root path address
                String baseLocation = getReaderContext().getResource().getURL().toString();
                // Add the Resource of the profile address to the actual resources, and load the corresponding bean definitions
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location) /* Calculate absolute path */, 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);
        }
    }
    // <6> After the resolution is successful, the listener is activated
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

The process of parsing the import tag is clear. The whole process is as follows:

  • <1> , gets the value of the source property, which represents the path of the resource.
    • <2> Resolve the system properties in the path, such as "${user.dir}".
  • <3> Determine whether the resource path location is an absolute path or a relative path. For detailed analysis, see 「 2.1 judgment path 」 .
  • <6> Notify the listener to finish parsing.

2.1 judgment path

Determine whether location is a relative path or an absolute path by the following code:

absoluteLocation = ResourcePatternUtils.isUrl(location) // <1>
    || ResourceUtils.toURI(location).isAbsolute(); // <2>

The rules for determining the absolute path are as follows:

  • <1> The absolute path begins with classpath *: or classpath:.
  • <1> Through this location, java.net.URL can be built as an absolute path.
  • <2> According to the location construction java.net.URI, judge whether to call the ා isAbsolute() method and determine whether it is an absolute path.

2.2 handling absolute paths

If the location is an absolute path, the load bean definitions (string location, set < resource > actualresources) method is called. This method is defined in org.springframework.beans.factory.support.AbstractBeanDefinitionReader. The code is as follows:

/**
 * Load bean definitions from the specified resource location.
 * <p>The location can also be a location pattern, provided that the
 * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
 * @param location the resource location, to be loaded with the ResourceLoader
 * (or ResourcePatternResolver) of this bean definition reader
 * @param actualResources a Set to be filled with the actual Resource objects
 * that have been resolved during the loading process. May be {@code null}
 * to indicate that the caller is not interested in those Resource objects.
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 * @see #getResourceLoader()
 * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
 * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
 */
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    // Get ResourceLoader object
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    }

    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.
        try {
            // Get the Resource array, because there may be multiple resources in the Pattern matching. For example, Ant style location
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            // Load beandefinitions
            int count = loadBeanDefinitions(resources);
            // Add to actualResources
            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);
        }
    } else {
        // Can only load single resources by absolute URL.
        // Get the Resource object,
        Resource resource = resourceLoader.getResource(location);
        // Load beandefinitions
        int count = loadBeanDefinitions(resource);
        // Add to actualResources
        if (actualResources != null) {
            actualResources.add(resource);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
        }
        return count;
    }
}

The whole logic is simple:

  • First, get the ResourceLoader object.
  • Then, different logic is executed according to different resourceloaders, mainly the possibility of multiple resources.
  • In the end, it will return to the xmlbeandefinitionreader ා loadbeandefinitions (Resource... Resources) method, so this is a recursive process.
  • In addition, the objects or arrays of the obtained resources will be added to the actual resources.

2.3 processing relative path

If location is a relative path, the Resource object of the corresponding relative path will be calculated according to the corresponding Resource, and then:

  • If the Resource exists, call xmlbeandefinitionreader ා loadbeandefinitions() method to load BeanDefinition.
  • Otherwise, construct an absolute location (that is, the code at StringUtils.applyRelativePath(baseLocation, location)) and call the ා loadbeandefinitions (string location, set < resource > actualresources) method, just like the absolute path procedure.

3. summary

At this point, the import tag is parsed, and the whole process is clearer and clearer: get the source attribute value, get the correct resource path, then call the XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) method to carry out the recursive BeanDefinition loading.

Published 22 original articles, won praise 1, visited 985
Private letter follow

Posted by gardner1 on Fri, 31 Jan 2020 21:48:34 -0800