Spring source code third bullet! What the hell is EntityResolver?

Last article I talked with my friends about the parsing process of XML files in Spring source code. I could have continued to look down to load core classes, but SongGe still hopes to be a little slower. Since I have to learn to understand, I can learn from XML There are also some other classes and concepts involved in the process of file parsing, so I will introduce them in several articles, and then we will continue to look at them.

This article is to introduce Last article The EntityResolver class involved in to see what this class is for.

This is the fourth article in the Spring source code series. Reading the previous articles will help you better understand this article

  1. Spring source code interpretation plan
  2. The first part of Spring source code is open and complete! How is the configuration file loaded?
  3. The second Spring source! XML file parsing process

First, let's review that in the EntityResolver class Last article Where is it.

When we talk about the doLoadDocument method, when we call the loadDocument method in the method, the second parameter passed is a EntityResolver instance. At that time, we said that this is used to handle the way of document verification, but how to deal with it, let's look at it today.

1.XML validation mode

To understand the EntityResolver, first look at the XML file validation schema.

At present, we may use JSON to transfer data in most cases. XML is rarely used. Some small partners may not be familiar with some rules of XML files. I'll talk about it a little here.

XML refers to the extensible markup language, which is a kind of markup language, similar to HTML. XML tags are not predefined, so users need to define their own tags, that is, the nodes in XML files are user-defined. XML files were designed to transfer data, not display it.

Generally speaking, an XML file consists of six parts:

  • Document life
  • element
  • attribute
  • notes
  • CDATA area
  • Processing instructions

Although the XML file itself does not have predefined XML tags, when the XML file is used as the configuration of the framework, there must be certain constraints on XML tags. Otherwise, everyone defines XML tags according to their own preferences, and the framework cannot read such XML files.

In XML technology, developers can constrain the tags in an XML document through a document, which is called constraint. XML that follows XML syntax is called well formed XML, while XML that follows XML constraints is called effective XML. XML constraint documents mainly define the names, attributes and order of elements that are allowed to appear in XML.

There are two ways to constrain XML tags:

  1. DTD
  2. Schema

DTD(Document Type Definition), the full name of which is document type definition. A DTD constraint file can be defined within an XML file, a local file, or a public DTD on a network.

XML Schema is also a kind of Schema language used to define and describe the structure and content of XML documents. Compared with DTD, Schema is more friendly to namespace support, but also supports more data types, and its constraint ability is relatively strong. In addition, it is very important that Schema documents themselves are also XML documents, rather than DTD The same use of self-contained grammar.

Therefore, at present, Schema has more advantages in XML constraints and is gradually replacing DTD.

You may have seen both constraints in daily development, but some people may not pay attention to them. Let me give you a simple example.

The early Spring configuration header is like this (Spring2.x), which is the DTD constraint:

<!--?xml version="1.0" encoding="UTF-8"?-->  
  
<beans>  
  
</beans>  

Now you can see that the Spring configuration header is generally like this. This is the Schema constraint:

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

</beans>

Schema constraints have a good support for namespaces. Namespaces can prevent naming conflicts. Namespace and constraint files in schema appear in pairs.

With constraints, what should be written and what should not be written in the XML file is fixed, so that the framework can successfully parse the XML file.

However, we also found a new problem. Whether DTD or Schema constraints, the constraint file address given is an online address, which means that the project must be able to access the online address when starting to load the constraint file. If the access to the online constraint file fails, the project will fail to start.

In order to solve this problem, the framework usually places constraint files locally. Where is it? It's actually in the jar package you downloaded. Take spring beans for example. There are two files in the downloaded jar package:

spring.handlers The contents of the document are as follows:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

This is actually a mapping configuration. The processing classes corresponding to each namespace are configured here.

spring.schemas The contents of the document are as follows (part):

http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.0.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.1.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.2.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans-4.3.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

As you can see, all versions and constraint files without version number correspond to the same file, namely org/springframework/beans/factory/xml/spring-beans.xsd , open the file directory, and we can see the constraint file:

So although the constraint file we see in Spring's XML configuration is an online address, in fact, the constraint file is read from the local jar.

2. Two kinds of parsers

EntityResolver is used to handle XML validation. Let's start with the definition of EntityResolver interface:

public interface EntityResolver {
    public abstract InputSource resolveEntity (String publicId,
                                               String systemId)
        throws SAXException, IOException;

}

There is only one method in the interface, which is to load the constraint file. In Spring, the implementation class of EntityResolver is DelegatingEntityResolver:

public class DelegatingEntityResolver implements EntityResolver {
	public static final String DTD_SUFFIX = ".dtd";
	public static final String XSD_SUFFIX = ".xsd";
	private final EntityResolver dtdResolver;
	private final EntityResolver schemaResolver;
	public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
		this.dtdResolver = new BeansDtdResolver();
		this.schemaResolver = new PluggableSchemaResolver(classLoader);
	}
	public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
		this.dtdResolver = dtdResolver;
		this.schemaResolver = schemaResolver;
	}
	@Override
	@Nullable
	public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
			throws SAXException, IOException {
		if (systemId != null) {
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}
		return null;
	}
	@Override
	public String toString() {
		return "EntityResolver delegating " + XSD_SUFFIX + " to " + this.schemaResolver +
				" and " + DTD_SUFFIX + " to " + this.dtdResolver;
	}
}

In the DelegatingEntityResolver class:

  1. Firstly, two different suffixes are used to distinguish different constraints.
  2. Then two different variables, dtdResolver and schemaResolver, are defined. The corresponding types are BeansDtdResolver and PluggableSchemaResolver. That is to say, constraint validation of dtd and schema is handled by these two classes respectively.
  3. In the resolveEntity method, different suffixes are parsed, and they are handed over to different entityresolvers for processing. There are two parameters in resolveEntity resolution. If it is dtd resolution, the publicId has a value. If it is schema resolution, the publicId is null, and the systemId always points to the specific constraint file.

Since most of them are schema constraints, let's focus on the implementation of PluggableSchemaResolver class:

public class PluggableSchemaResolver implements EntityResolver {
	public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
	private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);
	@Nullable
	private final ClassLoader classLoader;
	private final String schemaMappingsLocation;
	@Nullable
	private volatile Map<string, string> schemaMappings;
	public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
		this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
	}
	public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
		Assert.hasText(schemaMappingsLocation, "'schemaMappingsLocation' must not be empty");
		this.classLoader = classLoader;
		this.schemaMappingsLocation = schemaMappingsLocation;
	}
	@Override
	@Nullable
	public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Trying to resolve XML entity with public id [" + publicId +
					"] and system id [" + systemId + "]");
		}
		if (systemId != null) {
			String resourceLocation = getSchemaMappings().get(systemId);
			if (resourceLocation == null &amp;&amp; systemId.startsWith("https:")) {
				resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
			}
			if (resourceLocation != null) {
				Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
				try {
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isTraceEnabled()) {
						logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
					}
					return source;
				}
				catch (FileNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
					}
				}
			}
		}
		return null;
	}
	private Map<string, string> getSchemaMappings() {
		Map<string, string> schemaMappings = this.schemaMappings;
		if (schemaMappings == null) {
			synchronized (this) {
				schemaMappings = this.schemaMappings;
				if (schemaMappings == null) {
					try {
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
						schemaMappings = new ConcurrentHashMap&lt;&gt;(mappings.size());
						CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
						this.schemaMappings = schemaMappings;
					}
					catch (IOException ex) {
						throw new IllegalStateException(
								"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
					}
				}
			}
		}
		return schemaMappings;
	}
	@Override
	public String toString() {
		return "EntityResolver using schema mappings " + getSchemaMappings();
	}
}
  1. In this class, first pass the default_ SCHEMA_ MAPPINGS_ The location variable defines spring.schemas Location of the file.
  2. The getschemamapping method is to spring.schemas The contents of the file are read as a Map and loaded in.
  3. In the resolveEntity method, find the file path according to systemId, which is http\://www.springframework.org/schema/beans/spring-beans.xsd Format, the file path is org/springframework/beans/factory/xml/spring-beans.xsd If it is not loaded for the first time, replace the user's https: with http: and then load it.
  4. With the file path, call ClassPathResource to get a Resource object. For this, please refer to this series Part II , I will not repeat here.
  5. Finally, construct an InputSource to return.

stay Last article In, we get EntityResolver through getEntityResolver method:

protected EntityResolver getEntityResolver() {
	if (this.entityResolver == null) {
		// Determine default EntityResolver to use.
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader != null) {
			this.entityResolver = new ResourceEntityResolver(resourceLoader);
		}
		else {
			this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
		}
	}
	return this.entityResolver;
}

Finally, the ResourceEntityResolver is returned here. The ResourceEntityResolver inherits from DelegatingEntityResolver. When the resolveEntity method is called, the method of the parent class is also called first for processing. If the parent class method is processed successfully, the result given by the parent class method is directly returned. If the parent class method fails, the ResourceEntityResolver Try loading again through the relative path of the resource in.

3. Summary

Well, after the above introduction, I believe you have a certain understanding of XMl constraints and EntityResolver.

Postscript

As like as two peas in the WeChat group, a little buddy asked the same question:

Song can't help but sigh that the source code is not far from us. Reading the source code can effectively solve some real problems in our daily development!

If you feel that you have a harvest, remember to look at the encouragement and brother WeChat to search WeChat official account. A reply to 888 is to get the 17k star open source project learning document.

</string,></string,></string,>

Keywords: Programming xml Spring encoding JSON

Added by ureck on Mon, 29 Jun 2020 05:02:29 +0300