XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)解析过程

本文详细解析了Spring中BeanDefinitionReader、XmlBeanDefinitionReader和相关类的作用,重点介绍了如何防止循环加载资源,以及BeanDefinition的读取、解析和注册过程。

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

以下几个常见的Spring加载bean的相关类,一定要熟悉,面试官可以不问你,但你不能不会~

 接口定义org.springframework.beans.factory.support.BeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource)

实现类

我们关注Xml文件的读取,点开查看;

 org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource)

/**
	 * Load bean definitions from the specified XML file.
	 * @param resource the resource descriptor for the XML file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

对Resource资源进行了一层封装,主要是为了对 Resource 进行编码,保证内容读取的正确性

接着继续点进去

/**
 * 当前线程,正在加载的 EncodedResource 集合。
 */
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	Assert.notNull(encodedResource, "EncodedResource must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Loading XML bean definitions from " + encodedResource);
	}

	// <1> 获取已经加载过的资源
	Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
	if (currentResources == null) {
		currentResources = new HashSet<>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	if (!currentResources.add(encodedResource)) { // 将当前资源加入记录中。如果已存在,抛出异常
		throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
		// <2> 从 EncodedResource 获取封装的 Resource ,并从 Resource 中获取其中的 InputStream
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) { // 设置编码
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			// 核心逻辑部分,执行加载 BeanDefinition
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		} finally {
			inputStream.close();
		}
	} catch (IOException ex) {
		throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);
	} finally {
		// 从缓存中剔除该资源 <3>
		currentResources.remove(encodedResource);
		if (currentResources.isEmpty()) {
			this.resourcesCurrentlyBeingLoaded.remove();
		}
	}
}

通过 resourcesCurrentlyBeingLoaded.get() 代码,来获取已经加载过的资源,然后将 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。

避免一个 EncodedResource 在加载时,还没加载完成,又加载自身,从而导致死循环

从 encodedResource 获取封装的 Resource 资源,并从 Resource 中获取相应的 InputStream ,然后将 InputStream 封装为 InputSource ,最后调用 #doLoadBeanDefinitions(InputSource inputSource, Resource resource) 方法,执行加载 Bean Definition 的真正逻辑。

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions

/**
 * Actually load bean definitions from the specified XML file.
 * @param inputSource the SAX InputSource to read from
 * @param resource the resource descriptor for the XML file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 * @see #doLoadDocument
 * @see #registerBeanDefinitions
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		// <1> 获取 XML Document 实例
		Document doc = doLoadDocument(inputSource, resource);
		// <2> 根据 Document 实例,注册 Bean 信息
		int count = registerBeanDefinitions(doc, resource);
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + count + " bean definitions from " + resource);
		}
		return count;
	} catch (BeanDefinitionStoreException ex) {
		throw ex;
	} catch (SAXParseException ex) {
		throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
	} catch (SAXException ex) {
		throw new XmlBeanDefinitionStoreException(resource.getDescription(),
				"XML document from " + resource + " is invalid", ex);
	} catch (ParserConfigurationException ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"Parser configuration exception parsing XML from " + resource, ex);
	} catch (IOException ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"IOException parsing XML document from " + resource, ex);
	} catch (Throwable ex) {
		throw new BeanDefinitionStoreException(resource.getDescription(),
				"Unexpected exception parsing XML document from " + resource, ex);
	}
}

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument 

/**
	 * Actually load the specified document using the configured DocumentLoader.
	 * @param inputSource the SAX InputSource to read from
	 * @param resource the resource descriptor for the XML file
	 * @return the DOM Document
	 * @throws Exception when thrown from the DocumentLoader
	 * @see #setDocumentLoader
	 * @see DocumentLoader#loadDocument
	 */
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

获取指定资源(xml)的验证模式,获取 XML Document 实例

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions

// AbstractBeanDefinitionReader.java
private final BeanDefinitionRegistry registry;

// XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// <1> 创建 BeanDefinitionDocumentReader 对象
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// <2> 获取已注册的 BeanDefinition 数量
	int countBefore = getRegistry().getBeanDefinitionCount();
	// <3> 创建 XmlReaderContext 对象
	// <4> 注册 BeanDefinition
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 计算新注册的 BeanDefinition 数量
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

调用 #createBeanDefinitionDocumentReader() 方法,实例化 BeanDefinitionDocumentReader 对象。定义读取 Document 并注册 BeanDefinition 功能

  • <2> 处,调用 BeanDefinitionRegistry#getBeanDefinitionCount() 方法,获取已注册的 BeanDefinition 数量。
  • <3> 处,调用 #createReaderContext(Resource resource) 方法,创建 XmlReaderContext 对象。
  • <4> 处,调用 BeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext) 方法,读取 XML 元素,注册 BeanDefinition 们。
  • <5> 处,计算新注册的 BeanDefinition 数量。

createBeanDefinitionDocumentReader

/**
 * documentReader 的类
 *
 * @see #createBeanDefinitionDocumentReader() 
 */
private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
	return BeanUtils.instantiateClass(this.documentReaderClass);
}

 

public interface BeanDefinitionDocumentReader {

	/**
	 * Read bean definitions from the given DOM document and
	 * register them with the registry in the given reader context.
	 * @param doc the DOM document
	 * @param readerContext the current context of the reader
	 * (includes the target registry and the resource being parsed)
	 * @throws BeanDefinitionStoreException in case of parsing errors
	 */
	void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
			throws BeanDefinitionStoreException;

}

从给定的 Document 对象中解析定义的 BeanDefinition 并将他们注册到注册表中

DefaultBeanDefinitionDocumentReader

@Nullable
private XmlReaderContext readerContext;

@Nullable
private BeanDefinitionParserDelegate delegate;
    
/**
 * This implementation parses bean definitions according to the "spring-beans" XSD
 * (or DTD, historically).
 * <p>Opens a DOM Document; then initializes the default settings
 * specified at the {@code <beans/>} level; then parses the contained bean definitions.
 */
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    // 获得 XML Document Root Element
    // 执行注册 BeanDefinition
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

/**
 * Register each bean definition within the given root {@code <beans/>} element.
 */
@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
    // Any nested <beans> elements will cause recursion in this method. In
    // order to propagate and preserve <beans> default-* attributes correctly,
    // keep track of the current (parent) delegate, which may be null. Create
    // the new (child) delegate with a reference to the parent for fallback purposes,
    // then ultimately reset this.delegate back to its original (parent) reference.
    // this behavior emulates a stack of delegates without actually necessitating one.
    // 记录老的 BeanDefinitionParserDelegate 对象
    BeanDefinitionParserDelegate parent = this.delegate;
    // <1> 创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate
    this.delegate = createDelegate(getReaderContext(), root, parent);
    // <2> 检查 <beans /> 根标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans
    if (this.delegate.isDefaultNamespace(root)) {
        // <2.1> 处理 profile 属性。可参见《Spring3自定义环境配置 <beans profile="">》http://nassir.iteye.com/blog/1535799
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            // <2.2> 使用分隔符切分,可能有多个 profile 。
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            // <2.3> 如果所有 profile 都无效,则不进行注册
            // We cannot use Profiles.of(...) since profile expressions are not supported
            // in XML config. See SPR-12458 for details.
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    // <3> 解析前处理
    preProcessXml(root);
    // <4> 解析
    parseBeanDefinitions(root, this.delegate);
    // <5> 解析后处理
    postProcessXml(root);

    // 设置 delegate 回老的 BeanDefinitionParserDelegate 对象
    this.delegate = parent;
}

<1> 处,创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate 。BeanDefinitionParserDelegate 是一个重要的类,它负责解析 BeanDefinition

protected BeanDefinitionParserDelegate createDelegate(
        XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) {
    // 创建 BeanDefinitionParserDelegate 对象
    BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
    // 初始化默认
    delegate.initDefaults(root, parentDelegate);
    return delegate;
}

parseBeanDefinitions

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // <1> 如果根节点使用默认命名空间,执行默认解析
    if (delegate.isDefaultNamespace(root)) {
        // 遍历子节点
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                // <1> 如果该节点使用默认命名空间,执行默认解析
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                // 如果该节点非默认命名空间,执行自定义解析
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    // <2> 如果根节点非默认命名空间,执行自定义解析
    } else {
        delegate.parseCustomElement(root);
    }
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import
		importBeanDefinitionResource(ele);
	} else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias
		processAliasRegistration(ele);
	} else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean
		processBeanDefinition(ele, delegate);
	} else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

 

 

### Spring 中 transactionXml.xml 文件 XML 验证错误的解决方案 当遇到 `XmlBeanDefinitionStoreException` 异常时,通常表明在加载 Spring 的 XML 配置文件过程中出现了问题。具体到 `transactionXml.xml` 文件中的第 24 行和第 30 行报错,可能是由于以下几个常见原因引起的。 --- #### 可能的原因分析 1. **命名空间声明不完整或错误** 如果 XML 文件中使用了 `<tx>` 或其他自定义标签,则需要确保文件头部正确声明了对应的命名空间以及其 XSD 地址[^7]。缺少这些声明可能会导致解析器无法识别某些标签。 2. **XML 结构不符合 Schema 定义** 检查第 24 行和第 30 行附近的内容是否严格遵循指定的 XSD 规范。任何多余的属性、非法字符或是未闭合的标签都会引发验证失败[^8]。 3. **事务管理器配置不当** 若 `<tx:annotation-driven/>` 或者 `<tx:advice>` 等标签被错误地放置于不适合的位置(例如根节点外),同样会触发此类异常情况[^9]。 4. **版本兼容性问题** 使用的不同版本之间可能存在 API 差异。假如当前项目的依赖库与实际编写的 XML 配置存在版本差异,则极有可能造成解析上的矛盾冲突现象出现[^10]. 5. **重复 ID 或名称冲突** 在多个地方定义相同名字的服务组件或者代理对象也会引起类似的错误消息反馈给开发者查看修改之处以便顺利启动服务端口监听等待客户端请求连接进来完成整个交互过程[^11]. --- #### 解决方案 以下是针对上述潜在问题的具体解决办法: ##### 方法一:检查并完善 XML 命名空间声明 确保 `transactionXml.xml` 文件开头部分包含完整的标准头信息如下所示: ```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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/lang https://www.springframework.org/schema/lang/spring-lang.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> </beans> ``` ##### 方法二:校正具体的 Tx 配置项 重新审视第 24 行和第 30 行附近的配置内容。下面提供了一段典型的基于注解驱动的事务处理样例代码供参考: ```xml <!-- Enable annotation-based transaction management --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- Define the actual Transaction Manager bean --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- Optional - Example of defining an advice with explicit method rules --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- All public methods require transactions by default --> <tx:method name="*" propagation="REQUIRED"/> <!-- Specific read-only operations can be optimized this way --> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression="execution(* com.example.service..*.*(..))" id="serviceOperationPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperationPointCut"/> </aop:config> ``` ##### 方法三:审查事务管理器及相关 Bean 配置 确认已经适当设置了事务管理器 Bean,并且它能够成功初始化。例如: ```xml <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> ``` ##### 方法四:验证 XML Schema 合法性 如果近期升级了 Spring 版本,请务必保证所有的 XSD 地址均指向最新版。可通过查阅 [Spring Official Documentation](https://docs.spring.io/) 来获取最新的 schema URL。 --- #### 总结 通过以上几个方面的细致核查——从基础的命名空间声明完整性检验到高级别的事务管理器设置合理性评估——应该可以帮助定位并最终消除 `transactionXml.xml` 文件中存在的 XML 验证错误问题。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值