spring学习(三)——BeanDefinition的拼装(资源验证和Document的获取)

参考文章:

http://www.iocoder.cn/

IOC加载 BeanDefinitions的核心逻辑 

主要在XmlBeanDefinitionReader.#doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法中

加载Definitions的流程

  1. 调用 #getValidationModeForResource(Resource resource) 方法,获取指定资源(xml)的验证模式
  2. 调用 DocumentLoader#loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) 方法,获取 XML Document 实例。
  3. 调用 #registerBeanDefinitions(Document doc, Resource resource) 方法,根据获取的 Document 实例,注册 Bean 信息。

资源验证

资源验证类型:DTD 与 XSD 的区别

DTD

DTD(Document Type Definition),即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。其实 DTD 就相当于 XML 中的 “词汇”和“语法”,我们可以通过比较 XML 文件和 DTD 文件 来看文档是否符合规范,元素和标签使用是否正确。

要在 Spring 中使用 DTD,需要在 Spring XML 文件头部声明:



XSD

针对 DTD 的缺陷,W3C 在 2001 年推出 XSD。XSD(XML Schemas Definition)即 XML Schema 语言。XML Schema 本身就是一个 XML文档,使用的是 XML 语法,因此可以很方便的解析 XSD 文档。

XmlBeanDefinitionReader参数

	/**
	 * Indicates that the validation should be disabled.
	 * 禁用验证模式
	 */
	public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;

	/**
	 * Indicates that the validation mode should be detected automatically.
	 * 自动获取验证模式
	 */
	public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;

	/**
	 * Indicates that DTD validation should be used.
	 * DTD验证模式
	 */
	public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;

	/**
	 * Indicates that XSD validation should be used.
	 * XSD验证模式
	 */
	public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;

核心的验证方法:getValidationModeForResource

	protected int getValidationModeForResource(Resource resource) {
		//获取验证模式
		int validationModeToUse = getValidationMode();
		//非自动验证则直接返回
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		//自动获取验证模式
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		// 返回默认的XSD验证模式
		return VALIDATION_XSD;
	}

获取验证模式:detectValidationMode

/**
	 * 自动获取验证模式的
	 */
	protected int detectValidationMode(Resource resource) {
		//已经被打开,则抛出异常
		if (resource.isOpen()) {
			throw new BeanDefinitionStoreException(
					"Passed-in Resource [" + resource + "] contains an open stream: " +
					"cannot determine validation mode automatically. Either pass in a Resource " +
					"that is able to create fresh streams, or explicitly specify the validationMode " +
					"on your XmlBeanDefinitionReader instance.");
		}

		InputStream inputStream;
		try {
			//打开输入流
			inputStream = resource.getInputStream();
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
					"Did you attempt to load directly from a SAX InputSource without specifying the " +
					"validationMode on your XmlBeanDefinitionReader instance?", ex);
		}

		try {
			//this.validationModeDetector 验证模式探测器
			return this.validationModeDetector.detectValidationMode(inputStream);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
					resource + "]: an error occurred whilst reading from the InputStream.", ex);
		}
	}

xml验证模式探测器:XmlValidationModeDetector.detectValidationMode

public int detectValidationMode(InputStream inputStream) throws IOException {
		// Peek into the file to look for DOCTYPE.
		// 封装Reader,验证文档类型
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		try {
			// 是否为 DTD 校验模式。默认为,非 DTD 模式,即 XSD 模式
			boolean isDtdValidated = false;
			String content;
			//循环,逐行读取 XML 文件的内容
			while ((content = reader.readLine()) != null) {
				content = consumeCommentTokens(content);
				// 跳过,如果是注释,或者
				if (this.inComment || !StringUtils.hasText(content)) {
					continue;
				}
				//包含 DOCTYPE 为 DTD 模式
				if (hasDoctype(content)) {
					isDtdValidated = true;
					break;
				}
				if (hasOpeningTag(content)) {
					// End of meaningful data...
					break;
				}
			}
			// 返回 VALIDATION_DTD or VALIDATION_XSD 模式
			return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
		}
		catch (CharConversionException ex) {
			// Choked on some character encoding...
			// Leave the decision up to the caller.
			return VALIDATION_AUTO;
		}
		finally {
			reader.close();
		}
	}

 

 

获取Document

 

Document 获取策略:DocumentLoader接口

public interface DocumentLoader {

	/**
	 */
	Document loadDocument(
			InputSource inputSource,//加载 Document 的 Resource 资源
			EntityResolver entityResolver,//方法参数,解析文件的解析器
			ErrorHandler errorHandler,//处理加载 Document 对象的过程的错误
			int validationMode,//验证模式
			boolean namespaceAware) //命名空间支持。如果要提供对 XML 名称空间的支持,则需要值为 true
			throws Exception;

}

相关实现类

DefaultDocumentLoader是其默认的实现类

加载Document:loadDocument

@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
		//创建DocumentBuilder的工厂
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		//创建DocumentBuilder实例
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		//解析数据并生成Document
		return builder.parse(inputSource);
	}

解析流程:

  1. 创建builder工厂
  2. 创建builder
  3. 解析数据为document

createDocumentBuilderFactory

创建builder工厂

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {
		// 创建 DocumentBuilderFactory
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		// 设置命名空间支持
		factory.setNamespaceAware(namespaceAware);

		if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
			//开启效验
			factory.setValidating(true);
			if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
				// Enforce namespace aware for XSD...
				// XSD模式下设置命名空间支持
				factory.setNamespaceAware(true);
				try {
					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.");
					pcex.initCause(ex);
					throw pcex;
				}
			}
		}

		return factory;
	}

createDocumentBuilder

创建builder

protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
			@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
			throws ParserConfigurationException {
		// 使用factory生成builder
		DocumentBuilder docBuilder = factory.newDocumentBuilder();
		// 设置 EntityResolver 属性 设置解析器
		if (entityResolver != null) {
			docBuilder.setEntityResolver(entityResolver);
		}
		// 设置 ErrorHandler 属性,错误处理器
		if (errorHandler != null) {
			docBuilder.setErrorHandler(errorHandler);
		}
		return docBuilder;
	}

重点:EntityResolver解析器是解析的重点

 

解析器:EntityResolver 接口

public abstract InputSource resolveEntity (
                    String publicId,    //被引用的外部实体的公共标识符,如果没有提供,则返回 null 
                    String systemId    //被引用的外部实体的系统标识符
                    )
        throws SAXException, IOException;

解析器来源于XmlBeanDefinitionReader的getEntityResolver()

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		//首先验证资源 getValidationModeForResource(resource)
		//然后拼装Document
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}
protected EntityResolver getEntityResolver() {
		if (this.entityResolver == null) {
			// Determine default EntityResolver to use.
			// 如果 ResourceLoader 不为 null,则根据指定的 ResourceLoader 创建一个 ResourceEntityResolver 对象
			ResourceLoader resourceLoader = getResourceLoader();
			if (resourceLoader != null) {
				this.entityResolver = new ResourceEntityResolver(resourceLoader);
			}
			//如果 ResourceLoader 为 null ,则创建 一个 DelegatingEntityResolver 对象。
			else {
				this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
			}
		}
		return this.entityResolver;
	}

 

相关实现类

ResourceEntityResolver

继承自 DelegatingEntityResolver 类,通过 ResourceLoader 来解析实体的引用

private final ResourceLoader resourceLoader;


	/**
	 * Create a ResourceEntityResolver for the specified ResourceLoader
	 * (usually, an ApplicationContext).
	 * @param resourceLoader the ResourceLoader (or ApplicationContext)
	 * to load XML entity includes with
	 */
	public ResourceEntityResolver(ResourceLoader resourceLoader) {
		super(resourceLoader.getClassLoader());
		this.resourceLoader = resourceLoader;
	}

DelegatingEntityResolver

实现 EntityResolver 接口,分别代理 dtd 的 BeansDtdResolver 和 xml schemas 的 PluggableSchemaResolver 。代码如下:

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

	/** Suffix for schema definition files. */
	public static final String XSD_SUFFIX = ".xsd";

	/**
	 * BeansDtdResolver  代理
	 */
	private final EntityResolver dtdResolver;
	/**
	 * PluggableSchemaResolver  代理
	 */
	private final EntityResolver schemaResolver;


	/**
	 * 默认
	 */
	public DelegatingEntityResolver(@Nullable ClassLoader classLoader) {
		this.dtdResolver = new BeansDtdResolver();
		this.schemaResolver = new PluggableSchemaResolver(classLoader);
	}

	/**
	 * 自定义
	 */
	public DelegatingEntityResolver(EntityResolver dtdResolver, EntityResolver schemaResolver) {
		Assert.notNull(dtdResolver, "'dtdResolver' is required");
		Assert.notNull(schemaResolver, "'schemaResolver' is required");
		this.dtdResolver = dtdResolver;
		this.schemaResolver = schemaResolver;
	}

通过实现EntityResolver ,应用可以自定义如何寻找【验证文件】的逻辑。

  • XSD 验证模式
    • publicId:null
    • systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
    • spring学习(三)——BeanDefinition的拼装(资源验证和Document的获取)_第1张图片
  • DTD 验证模式
    • publicId:-//SPRING//DTD BEAN 2.0//EN
    • systemId:http://www.springframework.org/dtd/spring-beans.dtd
    • DTD 验证模式

此方法的实现

  • DelegatingEntityResolver中的实现
	@Override
	@Nullable
	public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
		if (systemId != null) {
			//".dtd"
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			//".xsd"
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}
		return null;
	}
  • BeansDtdResolver中的实现
	@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('/');
			//获取spring-beans的位置
			int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
			//定位到
			if (dtdNameStart != -1) {
				String dtdFile = DTD_NAME + DTD_EXTENSION;
				if (logger.isTraceEnabled()) {
					logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
				}
				try {
					// 创建 ClassPathResource 对象
					Resource resource = new ClassPathResource(dtdFile, getClass());
					// 创建 InputSource 对象,并设置 publicId、systemId 属性
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isTraceEnabled()) {
						logger.trace("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);
					}
				}
			}
		}
		// 使用默认行为,从网络上下载
		// Fall back to the parser's default behavior.
		return null;
	}
  • PluggableSchemaResolver中的实现
	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) {
			// 获得 Resource 所在位置
			String resourceLocation = getSchemaMappings().get(systemId);
			if (resourceLocation != null) {
				// 创建 ClassPathResource
				Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
				try {
					//创建流,并且设置publicId和systemId
					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);
					}
				}
			}
		}

		// Fall back to the parser's default behavior.
		return null;
	}
	/**
	 * Load the specified schema mappings lazily.
	 */
	private Map getSchemaMappings() {
		Map schemaMappings = this.schemaMappings;
		// 双重检查锁,实现 schemaMappings 单例
		if (schemaMappings == null) {
			synchronized (this) {
				schemaMappings = this.schemaMappings;
				if (schemaMappings == null) {
					if (logger.isTraceEnabled()) {
						logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
					}
					try {
						// 以 Properties 的方式,读取 schemaMappingsLocation
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
						if (logger.isTraceEnabled()) {
							logger.trace("Loaded schema mappings: " + mappings);
						}
						//schemaMappings 初始化到schemaMappings
						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;
	}
  • ResourceEntityResolver中的实现
@Override
	@Nullable
	public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
		//// 调用父类的方法,进行解析
		InputSource source = super.resolveEntity(publicId, systemId);
		// 解析失败,resourceLoader 进行解析
		if (source == null && systemId != null) {
			// 获得 resourcePath ,即 Resource 资源地址
			String resourcePath = null;
			try {
				String decodedSystemId = URLDecoder.decode(systemId, "UTF-8");
				// 解析文件资源的相对路径(相对于系统根路径)
				String givenUrl = new URL(decodedSystemId).toString();
				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());
				//设置资源流publicId systemId
				source.setPublicId(publicId);
				source.setSystemId(systemId);
				if (logger.isDebugEnabled()) {
					logger.debug("Found XML entity [" + systemId + "]: " + resource);
				}
			}
		}
		return source;
	}

 

你可能感兴趣的:(#,Spring源码,源码)