Interpretation of spring source code - Analysis of IoC import label

In the previous article, we analyzed the process of registering BeanDefinition, in which we learned that there are two cases when parsing nodes and child nodes. For the tags of the default namespace, we process them through the DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate), For custom tags, the BeanDefinitionParserDelegate#parseCustomElement(Element ele) method is used for processing.


Here, we begin to interpret the resolution of the default namespace, #parsedefaultelement (element ele, beandefinitionparserdelegate) method code is as follows:

	public static final String NESTED_BEANS_ELEMENT = "beans";

	public static final String ALIAS_ELEMENT = "alias";

	public static final String NAME_ATTRIBUTE = "name";

	public static final String ALIAS_ATTRIBUTE = "alias";

	public static final String IMPORT_ELEMENT = "import";
	/**
	 * If the root node or child node adopts the default namespace, the default resolution method is adopted
	 * @param ele
	 * @param delegate
	 */
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		// import tag
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			//alias tag
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			//Handle bean tags, which is the core tag processing in spring
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// Handle beans Tags
			doRegisterBeanDefinitions(ele);
		}
	}
  • Through the above code, we can see that the default tag includes import, alias, bean and beans. This article will interpret the import parsing

1. Import case

Everyone who has experienced Spring configuration files knows that if the project is relatively large, the maintenance of configuration files will make people feel terrible. There are too many files. Imagine putting all configurations in one Spring In the XML configuration file, what kind of aftershock is not obvious?
In this case, Spring provides a modular idea. 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>

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

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

This greatly simplifies the complexity of post configuration maintenance and is easy to manage.

2. importBeanDefinitionResource

The defaultbeandefinitiondocumentreader #parsedefaultelement (element ele, beandefinitionparserdelegate) method allows the user to parse the import tag. The method code is as follows:

	protected void importBeanDefinitionResource(Element ele) {
		// 1. Get the value of node attribute resource
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		//2. Judge whether it is empty. If it is empty, return directly
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}

		//3. Resolve the system attribute ${user.dir}
		// Resolve system properties: e.g. "${user.dir}"
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

		// The actual Resource set, that is, the address of the import,
		Set<Resource> actualResources = new LinkedHashSet<>(4);

		// Discover whether the location is an absolute or relative URI
		// Check whether the path location is an absolute path or a relative path
		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?
		// Absolute path
		if (absoluteLocation) {
			try {
				// Resolve the location to get the resource and add it to the actual resources
				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 {
			//Relative path
			// No URL -> considering resource location as relative to the current file.
			try {
				int importCount;
				// Resolve the location path to get the Resource relativeResource of the relative path
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				//existence
				if (relativeResource.exists()) {
					// Load Definition in resource
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					// Add Resource to relativeResource
					actualResources.add(relativeResource);
				}
				else {
					// Get root path
					String baseLocation = getReaderContext().getResource().getURL().toString();
					// Obtain the Resource through the root path and relative path, add it to the actual resources, and load the corresponding BeanDefinition at the same time
					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);
			}
		}
		// After the resolution is successful, activate the listener
		Resource[] actResArray = actualResources.toArray(new Resource[0]);
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

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

  • <1> Get the value of the Resource attribute, which represents the path of the Resource.

  • <2> At, resolve the system attributes 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".

  • <4> If it is an absolute path, call the Bean's parsing process recursively for another parsing. For detailed analysis, see "2.2 processing absolute path".

  • <5> If it is a relative path, first calculate the absolute path to get the Resource, and then analyze it. For detailed analysis, see "2.3 processing relative paths".

  • <6> Notify the listener to complete the parsing.

  • The execution process of the above code is as follows:


2.1 judgment path

In the above code, the code for judging whether location is an absolute path is as follows:

absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
  • ResourcePatternUtils. If the isurl (location) starts with classpath *: or classpath:, it is an absolute path. You can use this location to build Java net. The URL is an absolute path
  • Build Java. Net based on location net. Call #isAbsolute() method to judge whether the URI is an absolute path

2.2 processing absolute path


If #loadbeandefinitions (string location, set < resource > actualresources) is an absolute path, the method is called. This party is on www springframework. beans. factory. support. Abstractbeandefinitionreader. The code is as follows:

	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 under Pattern matching. For example, Ant style location
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				// Load BeanDefinition
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					// Add to actualResources
					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 BeanDefinition
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				// Add to actualResources
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}
  • The UML diagram of the above code execution process is as follows





The whole logic is simple:

  • First, get the ResourceLoader object.
  • Then, different logic is executed according to different resourceloaders, mainly because there may be multiple resources.
  • Finally, it will return to the XmlBeanDefinitionReader#loadBeanDefinitions(Resource... resources) method, so this is a recursive process.
  • In addition, the object or array of the obtained Resource will be added to the actualResources.

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 (base location, location)) and call #loadbeandefinitions (string location, set < resource > actualresources) method, which is the same as the absolute path procedure.

3. Summary


So far, the import tag has been resolved. The whole process is relatively clear: obtain the source attribute value, get the correct resource path, and then call the xmlbeandefinitionreader\loadbeandefinitions (Resource... Resources) method to load the BeanDefinition recursively.

This article is published by AnonyStar and can be reproduced, but the source of the original text needs to be declared.
Admire the "art of elegant coding", firmly believe that practice makes perfect, and strive to change life
Welcome to wechat public account: yunqi short code to get more high-quality articles
Follow the author's blog for more articles: Yunqi simple code

Tags: Java

Posted by Alith7 on Fri, 20 May 2022 11:21:55 +0300