spring源码:自定义EntityResolver加载xml的验证文件

一、目的

前面提到过XML为了保证文档结构的正确性,可以使用两种验证模式:DTD和XSD;这两种验证模式对应两种验证文件
SAX解析XML文档时,先读取该XML文档上声明的验证模式,然后根据声明去寻找对应的验证文件。默认的寻找规则是通过网络方式来读取验证文件,在网络传输的过程中会有各种各样的问题。 何况,本地写个demo没网就不能运行,本身就是个扯淡的事儿!
EntityResolver的作用是项目本身可以提供一个如何寻找验证文件的方法,Spring就实现了一个EntityResolver接口来实现不通过网络读取验证文件。

二、主要相关类

  • org.springframework.beans.factory.xml.XmlBeanDefinitionReader:获取EntityResolver的入口
  • org.springframework.beans.factory.xml.ResourceEntityResolver:EntityResolver的实现
  • org.springframework.beans.factory.xml.DelegatingEntityResolver:EntityResolver的实现,是ResourceEntityResolver的父类。真正的委托逻辑在这个类中,具体的实现在下面这俩类里面。
  • org.springframework.beans.factory.xml.BeansDtdResolver:DTD验证模式解析器
  • org.springframework.beans.factory.xml.PluggableSchemaResolver:XSD验证模式解析器

三、源码分析

  1. XmlBeanDefinitionReader的doLoadDocument方法为加载xml文档做数据准备
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		// 真正加载xml文档的类是DocumentLoader,我们主要分析第二个入参
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

	protected EntityResolver getEntityResolver() {
		if (this.entityResolver == null) {
			// Determine default EntityResolver to use.
			ResourceLoader resourceLoader = getResourceLoader();
			if (resourceLoader != null) {
				// ResourceEntityResolver为DelegatingEntityResolver的子类,在调用resolveEntity()方法来自定义寻找规则时
				// 也是调用了父类的resolveEntity(),一般父类就能找到验证模式声明了,所以我们直接看DelegatingEntityResolver的resolveEntity()
				this.entityResolver = new ResourceEntityResolver(resourceLoader);
			}
			else {
				this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
			}
		}
		return this.entityResolver;
	}
  1. 自定义寻找验证模式的规则DelegatingEntityResolver的resolveEntity()
	// 先看下DelegatingEntityResolver的构造方法,创建了两种类型的EntityResolver
	public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
		this.dtdResolver = new BeansDtdResolver();
		this.schemaResolver = new PluggableSchemaResolver(classLoader);
	}
	
	@Override
	@Nullable
	public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
		if (systemId != null) {
			// 以.dtd结尾使用DTD解析器(3)
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			// 以.xsd结尾使用schema解析器(4)
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}
		return null;
	}
  1. DTD解析器BeansDtdResolver的实现
    两个入参的值是在XML文档中声明的验证模式获取的,如下
    publicId=-//SPRING//DTD BEAN 2.0//EN
    systemId=http://www.springframework.org/dtd/spring-beans.dtd
	@Override
	@Nullable
	public InputSource resolveEntity(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('/');
			// 并且systemId字符串中包含“spring-beans”
			int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
			if (dtdNameStart != -1) {
				// 文件名:spring-beans.dtd
				String dtdFile = DTD_NAME + DTD_EXTENSION;
				if (logger.isTraceEnabled()) {
					logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
				}
				try {
					// 使用ClassPathResource来读取跟当前类(getClass())同路径下的spring-beans.dtd文件
					// 也就是说org.springframework.beans.factory.xml这个包下的spring-beans.dtd文件,见(6)
					// 这样就不用通过网络获取DTD验证文件了
					Resource resource = new ClassPathResource(dtdFile, getClass());
					// 包装成InputSource 
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isDebugEnabled()) {
						logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
					}
					return source;
				}
				catch (IOException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
					}
				}

			}
		}

		// Use the default behavior -> download from website or wherever.
		return null;
	}
  1. XSD解析器PluggableSchemaResolver的实现
    两个入参的值是在XML文档中声明的验证模式获取的,如下
    publicId=null
    systemId=http://www.springframework.org/schema/beans/spring-beans.xsd
	// 先看下构造方法
	public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
		// schemaMapping路径为META-INF/spring.schemas
		// 与DTD不一样的是,XSD的验证文件路径不是拼接得到的,而是从META-INF/spring.schemas获取的
		this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
	}
	
	@Override
	@Nullable
	public InputSource resolveEntity(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) {
			// 从schemaMappings中通过systemId获取XSD文件路径;schemaMappings获取方式在getSchemaMappings()中
			// 获取的值为:org/springframework/beans/factory/xml/spring-beans.xsd
			String resourceLocation = getSchemaMappings().get(systemId);
			if (resourceLocation != null) {
				// 使用ClassPathResource加载对应的xsd文件
				// 也就是说org/springframework/beans/factory/xml/spring-beans.xsd文件,见(6)
				Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
				try {
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isDebugEnabled()) {
						logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
					}
					return source;
				}
				catch (FileNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
					}
				}
			}
		}
		return null;
	}

	// 生成schemaMappings
	private Map<String, String> getSchemaMappings() {
		Map<String, String> schemaMappings = this.schemaMappings;
		if (schemaMappings == null) {
			synchronized (this) {
				schemaMappings = this.schemaMappings;
				if (schemaMappings == null) {
					if (logger.isDebugEnabled()) {
						logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
					}
					try {
						// schemaMappingsLocation的值为META-INF/spring.schemas,在上面的构造方法中可以看到
						// 读取完文件,得到一个键值对对象。文件内容见(5)
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
						if (logger.isDebugEnabled()) {
							logger.debug("Loaded schema mappings: " + mappings);
						}
						Map<String, String> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
						// 把mappings 合并到mappingsToUse然后返回
						CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
						schemaMappings = mappingsToUse;
						this.schemaMappings = schemaMappings;
					}
					catch (IOException ex) {
						throw new IllegalStateException(
								"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
					}
				}
			}
		}
		return schemaMappings;
	}
  1. xsd文件相关键值对文件:META-INF/spring.schemas
    6. 两个验证文件在这里插入图片描述

四、总结

  1. XmlBeanDefinitionReader把创建好的ResourceEntityResolver对象,传给DefaultDocumentLoader来自定义获取验证模式声明文件的方式。
  2. DocumentBuilder的parse方法在解析xml文档的时候,会调用自定义ResourceEntityResolver的resolveEntity()方法。
  3. ResourceEntityResolver的resolveEntity()方法会先调用父类DelegatingEntityResolver的resolveEntity()方法。
  4. DelegatingEntityResolver的resolveEntity()方法中根据不同的验证模式分发给真正干活儿的:BeansDtdResolver和PluggableSchemaResolver
  5. DTD验证模式,直接在BeansDtdResolver使用ClassPathResource读取org/springframework/beans/factory/xml/spring-beans.dtd文件即可。
  6. XSD验证模式,先把META-INF/spring.schemas文件的内容映射为一个Properties对象。然后根据传入的systemId值,获取到对应的xsd文件名,进而使用ClassPathResource读取。

五、借鉴

  1. org.xml.sax.EntityResolver
    本次我们借鉴的不是spring,而是SAX程序的设计。通常我们设计多功能的程序,可由用户选择不同的功能,是通过用户传入不同的参数,我们程序实现不同的功能。如用户传入1,我们设置关灯;用户传入2,我们设置开灯。
    其实我们更可以让用户传入自定义的代码,实现办法就是我们的某个控制功能的入参是一个接口。用户如果想扩展我们的程序功能,就可以实现这个接口的某个方法,然后我们在特定的时机就调用这个接口的那个方法。
    EntityResolver的实现:
    在DocumentBuilder的parse方法解析xml文档时,会调用EntityResolver的resolveEntity()方法来获取验证xml文档的声明文件,从而验证xml文档格式的正确性。其中EntityResolver就可以由用户自定义。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值