老生谈spring(四):xml的解析

本文详细探讨了Spring框架中XML配置文件的加载过程,从loadBeanDefinitions方法开始,逐步解析Document对象,并通过BeanDefinitionDocumentReader进行注册。在解析过程中,重点关注了parseBeanDefinitions方法,它是XML配置真正解析的关键,负责处理bean、import、alias等标签。文章指出,理解Spring源码需要耐心,通过跟踪方法调用链,可以更好地掌握Spring的工作原理。

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

老生谈spring(四):xml的解析

1、接着上一节,我们继续看loadBeanDefinitions代码(代码被我简化成伪代码)

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		
		if (resourceLoader instanceof ResourcePatternResolver) {
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//加载多个xml文件
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					Collections.addAll(actualResources, resources);
				}
				return count;
			}
			catch (IOException ex) {
				...
			}
		}
		else {
			Resource resource = resourceLoader.getResource(location);
			//加载一个xml文件
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			return count;
		}
	}

2、其实loadBeanDefinitions就算load的是Resource数组,也是循环调用loadBeanDefinitions去加载xml文件,因此我们主要看父类的重载方法loadBeanDefinitions,这个方法在XmlBeanDefinitionReader给出了实现。(又是调了重载方法,我直接把调用的方法也贴在一起)

	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException("check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
			    //通过Resource拿到xml文件的输入流封装成InputResource对象,
			    //再调doLoadBeanDefinitions方法进行解析
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			...
		}
		finally {
			...
		}
	}

3、doLoadBeanDefinitions方法就很清晰明了,通过inputSource获取到这个xml的Document对象,然后就调用registerBeanDefinitions对这个xml的bean对象进行注册,而registerBeanDefinitions方法里创建了一个BeanDefinitionDocumentReader对象对doc进行解析注册

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (Exception ex) {
		   ...
		}
	}
	
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

4、registerBeanDefinitions方法就是调用了doRegisterBeanDefinitions方法进行xml的处理,而doRegisterBeanDefinitions里又调用了parseBeanDefinitions方法,parseBeanDefinitions方法就是真正的对xml进行解析的方法。

	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}
	
	protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);
		//可以对root对象进行扩展
		preProcessXml(root);
		//开始解析xml
		parseBeanDefinitions(root, this.delegate);
		//可以对root对象进行扩展
		postProcessXml(root);

		this.delegate = parent;
	}

5、兜兜转转那么久终于到了parseBeanDefinitions方法,这个方法就是真正的解析xml的方法了。这个方法就是把root节点下的所有子节点循环遍历然后调用parseDefaultElement方法解析这些子节点

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		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;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

6、来到parseDefaultElement就可以看到这里会对import、alias、bean以及beans标签的处理,由于impo标签和beans标签最终还是会变成bean标签的解析,所以就不展开了讲了,而alias标签也就给beanName起个别名也不展开讲,所以下面的篇章重点将bean标签是怎么封装成BeanDefinition然后注册到spring容器的。

	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);
		}
	}

7、结语:spring的代码是比较绕的,所以在阅读spring源码时需要读者有足够的耐心跟着文章中的步骤一步步地找到源码中的方法,多试几次就能比较好的了解spring的一个调用链了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值