spring IOC根据xml文件获取Document对象分析

写在前面

本文在这篇文章的基础上继续分析,这篇文章分析了如何获取验证模式,获取完验证模式之后就是将xml文件生成对应的文档对象了。

1:作用

用来从中解析用户配置的bean等信息为BeanDefinition数据结构。

2:测试代码

为了方便调试再贴下测试代码:

@Test
public void testBeanDefinitionLoad() {
    // 定义资源
    ClassPathResource classPathResource = new ClassPathResource("testbeandefinition.xml");
    // 定义IOC容器
    DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
    // 定义bean定义读取器
    XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
    // 通过bean定义读取器从资源中读取bean定义
    int i = xmlBeanDefinitionReader.loadBeanDefinitions(classPathResource);
    System.out.println("bean定义的个数是:" + i);
}

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">

    <bean id="testBeanDefinitionBean"
          class="yudaosourcecode.spring.TestBeanDefinitionBean"></bean>

    <bean id="testBeanDefinitionBean1"
          class="yudaosourcecode.spring.TestBeanDefinitionBean"></bean>
    <!-- 这里引入自己的话会发生org.springframework.beans.factory.BeanDefinitionStoreException异常 -->
    <!--<import resource="testbeandefinition.xml"/>-->
</beans>

最终调用到代码:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}

其中获取验证模式的代码getValidationModeForResource(resource),我们已经再这篇文章中进行了分析。我们分析的入口方法是this.documentLoader.loadDocument。我们先来分析this.documentLoader

3:DocumentLoader

DocumentLoader全类名为org.springframework.beans.factory.xml.DocumentLoader,是一个顶层接口。定义了从资源文件转换为Document文档对象的功能,只有一个方法如下:

org.springframework.beans.factory.xml.DocumentLoader
public interface DocumentLoader {
	Document loadDocument(
			InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
			throws Exception;
}

看下其中的参数:

inputSource:待解析的资源
entityResolver:解析文件的解析器
errorHandler:处理解析文件过程中发生的错误
validationMode:xml文件验证模式DTD(Document Type Definition) OR XSD(Xml Schema Definition)
namesapceAware:xml命名空间支持,true为支持,false为不支持

我们来debug看下这些参数:
在这里插入图片描述
该接口只有一个实现类org.springframework.beans.factory.xml.DefaultDocumentLoader,其实现的加载方法loadDocument我们单起一部分讲解。

3.1:loadDocument

源码如下:

org.springframework.beans.factory.xml.DefaultDocumentLoader#loadDocument
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
		ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
	// <2021-02-26 17:25> 创建文档构造器工厂
	DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
	if (logger.isTraceEnabled()) {
		logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
	}
	// <2021-02-26 17:26>通过文档构造器工厂创建文档构造器
	DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
	return builder.parse(inputSource);
}

<2021-02-26 17:25>处代码创建文档构造器工厂,其源码如下:

org.springframework.beans.factory.xml.DefaultDocumentLoader#createDocumentBuilderFactory
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {
	// 注意这里的类是jdk的rt.jar中的类,而非spring的了
	// 创建实例对象
	DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	// 设置命名空间支持
	factory.setNamespaceAware(namespaceAware);
	// 如果有验证模式,目前一般都是xsd VALIDATION_XSD
	if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
		// 开启验证
		factory.setValidating(true);
		// 如果是xsd强制开启命名空间支持
		if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
			// Enforce namespace aware for XSD...
			factory.setNamespaceAware(true);
			try {
				// 设置SCHEMA_LANGUAGE_ATTRIBUTE属性
				factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
			}
			catch (IllegalArgumentException ex) {
				ParserConfigurationException pcex = new ParserConfigurationException(
						"Unable to validate using XSD: Your JAXP provider [" + factory +
						"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
						"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
				// <2021-02-26 18:38>
				pcex.initCause(ex);
				throw pcex;
			}
		}
	}

	return factory;
}

<2021-02-26 18:38>代码是设置引起异常,关于方法initCause可以参考这里
<2021-02-26 17:26>处代码通过文档构造器工厂创建文档构造器
,其源码如下:

org.springframework.beans.factory.xml.DefaultDocumentLoader#createDocumentBuilder
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
			@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
			throws ParserConfigurationException {
	// 通过工厂创建文件构造器
	DocumentBuilder docBuilder = factory.newDocumentBuilder();
	// <2021-02-26 18:50>
	// 如果解析文件的解析器不为空,则设置
	if (entityResolver != null) {
		docBuilder.setEntityResolver(entityResolver);
	}
	// 如果错误处理handler不为空,则设置
	if (errorHandler != null) {
		docBuilder.setErrorHandler(errorHandler);
	}
	// 返回工厂构造器
	return docBuilder;
}

<2021-02-26 18:50>处代码是设置实体解析器,作用是查找约束文件,DTD类型就是对应的.dtd文件,XSD类型就是对应的.xsd文件,对应的接口是org.xml.sax.EntityResolver是在jdk的rt.jar中,并不是spring自己提供的功能,源码如下:

org.xml.sax.EntityResolver#resolveEntity
public interface EntityResolver {

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

}

可以看到只有一个方法resolveEntity,其中有两个参数,第一个是publicId,第二个是systemId,其中第二个参数systemId代表的是约束文件的地址,本例中的参数值如下图:
在这里插入图片描述
参数entityResolver的获取是在方法调用org.springframework.beans.factory.xml.DocumentLoader#loadDocument中通过org.springframework.beans.factory.xml.XmlBeanDefinitionReader#getEntityResolver方法获取的,该方法如下:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#getEntityResolver
protected EntityResolver getEntityResolver() {
	// 为null才进入,可以认为就是null
	if (this.entityResolver == null) {
		// <2021-02-28 09:32>
		// 获取资源加载器
		ResourceLoader resourceLoader = getResourceLoader();
		// 如果资源加载器不为空则直接new一个ResourceEntityResolver,这里执行最终返回的就是这个对象
		if (resourceLoader != null) {
			this.entityResolver = new ResourceEntityResolver(resourceLoader);
		}
		// 否则返回DelegatingEntityResolver对象
		else {
			this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
		}
	}
	// 返回最终的实体解析器
	return this.entityResolver;
}

<2021-02-28 09:32>处代码是获取资源加载器,关于资源加载器可以参考这里,如果有资源加载器,则创建一个资源实体解析器ResourceEntityResolver,该类定义如下:

public class ResourceEntityResolver extends DelegatingEntityResolver {}

其中DelegatingEntityResolver定义如下:

public class DelegatingEntityResolver implements EntityResolver {}

接下来我们看下EntityResolver的的具体子类实现查找约束文件的逻辑。

4:EntityResolver

4.1:DelegatingEntityResolver

这是spring实现EntityResolver的直接子类,该类是一个代理类,根据具体的验证模式是调用具体的子类进行验证,代理源码如下:

org.springframework.beans.factory.xml.DelegatingEntityResolver
public class DelegatingEntityResolver implements EntityResolver {

	/** Suffix for DTD files. */
	public static final String DTD_SUFFIX = ".dtd";

	/** Suffix for schema definition files. */
	public static final String XSD_SUFFIX = ".xsd";
	// dtd验证模式的实体解析器
	private final EntityResolver dtdResolver;
	// xsd验证模式的实体解析器
	private final EntityResolver schemaResolver;

	@Override
	@Nullable
	public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
			throws SAXException, IOException {
		// 有systemId才处理,systemId标记的是验证文件信息
		if (systemId != null) {
			// 如果是dtd则使用this.dtdResolver
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			// 如果是xsd则使用this.schemaResolver
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}

		// 都不符合,返回null,则后续使用默认的网络下载方式获取
		return null;
	}
}

4.2:BeansDtdResolver

该类源码如下:

org.springframework.beans.factory.xml.BeansDtdResolver
public class BeansDtdResolver implements EntityResolver {
	// 约束文件扩展名常量
	private static final String DTD_EXTENSION = ".dtd";
	// 约束文件的名称常量
	private static final String DTD_NAME = "spring-beans";

	private static final Log logger = LogFactory.getLog(BeansDtdResolver.class);


	@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 + "]");
		}
		// 地址信息以dtd结尾
		if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
			// 获取最后一个/的位置
			int lastPathSeparator = systemId.lastIndexOf('/');
			// 获取文件名地址
			int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
			// 存在文件名
			if (dtdNameStart != -1) {
				// 拼接生成dtd文件名
				String dtdFile = DTD_NAME + DTD_EXTENSION;
				if (logger.isTraceEnabled()) {
					logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
				}
				try {
					// 构造资源
					Resource resource = new ClassPathResource(dtdFile, getClass());
					// 构造InputSource
					InputSource source = new InputSource(resource.getInputStream());
					// 设置publicId
					source.setPublicId(publicId);
					// 设置systemId
					source.setSystemId(systemId);
					if (logger.isTraceEnabled()) {
						logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
					}
					// 返回
					return source;
				}
				catch (FileNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
					}
				}
			}
		}

		// 返回null,使用网络下载默认行为
		return null;
	}
}

4.3:PluggableSchemaResolver

该类有一个重要的属性DEFAULT_SCHEMA_MAPPINGS_LOCATION,如下:

public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";

该文件定义如下:
在这里插入图片描述
定义的是xsd的网络地址对应的jar包物理地址信息,最终会加载到变量private volatile Map<String, String> schemaMappings;其中加载代码如下:

Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
schemaMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;

加载完毕后如下图:
在这里插入图片描述
接下来看下获取资源方法:

org.springframework.beans.factory.xml.PluggableSchemaResolver#resolveEntity
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 + "]");
	}
	// 如果systemId不为空
	if (systemId != null) {
		// <2021-02-28 11:26>,从映射map中获取资源jar包中的地址
		String resourceLocation = getSchemaMappings().get(systemId);
		// 如果是没有获取到地址,则尝试使用http协议开头的网络地址获取
		if (resourceLocation == null && systemId.startsWith("https:")) {
			// Retrieve canonical http schema mapping even for https declaration
			resourceLocation = getSchemaMappings().get("http:" + systemId.substring(6));
		}
		// 如果是不为空,则创建ClassPathResource并返回
		if (resourceLocation != null) {
			Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
			try {
				InputSource source = new InputSource(resource.getInputStream());
				// 设置publicId,systemId
				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);
				}
			}
		}
	}

	// Fall back to the parser's default behavior.
	return null;
}

<2021-02-28 11:26>处代码是从META-INF/spring.schema文件中加载约束文件url地址和jar包中地址的映射关系,源码如下:

org.springframework.beans.factory.xml.PluggableSchemaResolver#getSchemaMappings
private Map<String, String> getSchemaMappings() {
		Map<String, String> schemaMappings = this.schemaMappings;
	if (schemaMappings == null) {
		synchronized (this) {
			schemaMappings = this.schemaMappings;
			if (schemaMappings == null) {
				if (logger.isTraceEnabled()) {
					logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
				}
				try {
					// 从spring.schema文件中加载验证文件URL和jar包地址映射关系
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
					if (logger.isTraceEnabled()) {
						logger.trace("Loaded schema mappings: " + mappings);
					}
					schemaMappings = new ConcurrentHashMap<>(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;
}

4.4:ResourceEntityResolver

源码:

org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
			throws SAXException, IOException {
	// <2021-02-28 12:40>,直接调用父类方法获取
	InputSource source = super.resolveEntity(publicId, systemId);
	// 如果是没有获取到,并且systemId不为null
	if (source == null && systemId != null) {
		String resourcePath = null;
		try {
			// 使用u8编码
			String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
			String givenUrl = new URL(decodedSystemId).toString();
			// <2021-02-28 12:41>
			// 获取file://协议的项目所在地址
			String systemRootUrl = new File("").toURI().toURL().toString();
			// Try relative to resource base if currently in system root.
			if (givenUrl.startsWith(systemRootUrl)) {
				resourcePath = givenUrl.substring(systemRootUrl.length());
			}
		}
		catch (Exception ex) {
			// Typically a MalformedURLException or AccessControlException.
			if (logger.isDebugEnabled()) {
				logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
			}
			// No URL (or no resolvable URL) -> try relative to resource base.
			resourcePath = systemId;
		}
		if (resourcePath != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
			}
			Resource resource = this.resourceLoader.getResource(resourcePath);
			source = new InputSource(resource.getInputStream());
			source.setPublicId(publicId);
			source.setSystemId(systemId);
			if (logger.isDebugEnabled()) {
				logger.debug("Found XML entity [" + systemId + "]: " + resource);
			}
		}
		else if (systemId.endsWith(DTD_SUFFIX) || systemId.endsWith(XSD_SUFFIX)) {
			// External dtd/xsd lookup via https even for canonical http declaration
			String url = systemId;
			if (url.startsWith("http:")) {
				url = "https:" + url.substring(5);
			}
			try {
				// 创建InputSource并设置publicId,sysetemId信息
				source = new InputSource(new URL(url).openStream());
				source.setPublicId(publicId);
				source.setSystemId(systemId);
			}
			catch (IOException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not resolve XML entity [" + systemId + "] through URL [" + url + "]", ex);
				}
				// Fall back to the parser's default behavior.
				source = null;
			}
		}
	}

	return source;
}

<2021-02-28 12:40>,这里调用的方法就是org.springframework.beans.factory.xml.DelegatingEntityResolver#resolveEntity已经在前面分析过。<2021-02-28 12:41>处代码是获取file://协议的项目根路径地址,如file:/Users/xb/Desktop/D/dongsir-dev/java-life-current/java-life/是我本地的结果值,用于从系统根路径查找约束文件,换句话说就是处理约束文件在系统根路径内的情况。

5:获取Document

最终调用方法org.springframework.beans.factory.xml.DefaultDocumentLoader#loadDocument获取文档对象,代码如下:

org.springframework.beans.factory.xml.DefaultDocumentLoader#loadDocument
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
		ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

	DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
	if (logger.isTraceEnabled()) {
		logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
	}
	DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
	return builder.parse(inputSource);
}

到这里已经成功过去了xml文件对应的Document,接下来就是分析如何解析为BeanDefinition了,详细参考这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值