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

本文深入探讨了Spring框架中IOC容器加载BeanDefinitions的核心逻辑,详细分析了XmlBeanDefinitionReader类如何处理XML配置文件,包括资源验证流程、DTD与XSD的区别、XmlBeanDefinitionReader参数解释及核心验证方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考文章:

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 文件头部声明:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd">

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 ,应用可以自定义如何寻找【验证文件】的逻辑。

此方法的实现

  • 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<String, String> getSchemaMappings() {
		Map<String, String> 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;
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值