3.2-2、Spring源码学习:reader.loadBeanDefinitions(configLocation);

前言

体能状态先于精神状态,习惯先于决心,聚焦先于喜好。

配置文件加载思路

本文讲述的是Spring 在加载配置文件时的流程,从 application-*.xml 这种到具体分类型接续。
其中有颜色背景的两个是for循环处理。
蓝色线表示递归调用。
红线部分表示别名。
Spring 对配置文件的处理思路即使,location-多个配置文件-每个配置文件区分 profile——每个 profile 区分四种标签 import、alias、beans、bean

在这里插入图片描述

AbstractBeanDefinitionReader.loadBeanDefinitions(

org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(
负责将加载到的bean definitions 文件解析为具体的bean实例
从指定的 location 资源路径加载 bean 定义信息。location 可以是简单的路径,但是也可以是 ResourcePatternResolver 类型,这样的话就需要对 ResourcePatternResolver 进行处理了,在初始阶段其包含一个 资源的set。
location 值可能为空,表示调用者对于资源不感兴趣。
最终返回发现的bean definitions 的数量

/**
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 * @see #getResourceLoader()
	 * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
	 * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
	 */
	public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//获取 资源加载器
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}
		//如果资源加载器属于 ResourcePatternResolver,则需要解析
		if (resourceLoader instanceof ResourcePatternResolver) {
			// 对 location 进行解析,Pattern模式可以生成多个 Resource 类型数组
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//具体的对资源进行解析-你可以看到,这里是一个递归调用
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// 使用 绝对URL只能加载单个的资源
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

将 ResourcePatternResolver 转化为 Resource

ResourcePatternResolver 类型表示资源包含多个 Resource的资源,而Spring 是以Resource作为基本单位进行Bean definitions 的解析的。
简单来说就是,加入你配置的是 classpath:application-*.xml,他就是寻找所有符合该通配符命名的文件,比如你有文件是 application-A.xml 、application-B.xml,那么就会生成两个Resource来进行解析,然后每个 resource就会进入 int loadCount = loadBeanDefinitions(resource);的逻辑

org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources(

//locationPattern 的形式如 classpath:applicationContext.xml 或 WEB-INF/*.xml等
@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");  
		//路径是否以 classpath*: 开头
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// 一个类路径资源-可能包含多个同名文件
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				//满足一个类路径的通配,Ant风格
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 所有的类资源使用给定的名字
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// 仅仅查找去除前缀的匹配,比如 classpath:123.xml,则只匹配123.xml
			// (避免奇怪的前缀).
			int prefixEnd = locationPattern.indexOf(":") + 1;
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// 查找 Ant 风格
				return findPathMatchingResources(locationPattern);
			}
			else {
				// 单个给定名字的文件
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}
AbstractBeanDefinitionReader.loadBeanDefinitions(

org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(

@Override
	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int counter = 0;
		for (Resource resource : resources) {
			counter += loadBeanDefinitions(resource);
		}
		return counter;
	}
XmlBeanDefinitionReader.loadBeanDefinitions(

org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(
从指定的xml文件加载 bean definitions

/**
	 * @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));
	}
	
/**
	 * @param encodedResource the resource descriptor for the XML file,
	 * allowing to specify an encoding to use for parsing the file
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
	 */
	public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				//xml文件以指定字符格式转化为了输入流,在这里进行解析
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}
XmlBeanDefinitionReader.doLoadBeanDefinitions(

org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions
对输入流进行具体解析,到这里才开始真正的解析xml文件

/**
	 * @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 {
			//将输入流转化为 Document 格式
			Document doc = doLoadDocument(inputSource, resource);
			//注册bean
			return registerBeanDefinitions(doc, resource);
		}
		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);
		}
	}
.XmlBeanDefinitionReader.registerBeanDefinitions(

org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions
注册 DOM document 中包含的 bean definitions

/**
	 * @param doc the DOM document
	 * @param resource the resource descriptor (for context information)
	 * @return the number of bean definitions found
	 * @throws BeanDefinitionStoreException in case of parsing errors
	 * @see #loadBeanDefinitions
	 * @see #setDocumentReaderClass
	 * @see BeanDefinitionDocumentReader#registerBeanDefinitions
	 */
	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;
	}
DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(
开始解析Document 文件了,但是依旧是委托给了另一个方法

@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}
DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(:解析 beans 标签

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(

  • beans 标签和 profile 标签一起使用,即多版本
 <beans profile="test2">
 	<import resource=""></import>
 	<beans profile="test1">
 		<import resource=""></import>
 		<beans profile="dev">
			<import resource=""></import>
		</beans>
 	</beans>
 </beans>

这个标签用于划分版本,指定多版本时可以指定一个为激活, 关于 profile标签的使用可以参考 Spring 中的 profile

解析 profile 标签

/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	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 parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
		    //profile 标签
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
			    //MULTI_VALUE_ATTRIBUTE_DELIMITERS 为  “,;” 
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isInfoEnabled()) {
						logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}

		//空实现
		preProcessXml(root);
		//真正开始处理节点
		parseBeanDefinitions(root, this.delegate);
		//空实现
		postProcessXml(root);

		this.delegate = parent;
	}
DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(:准备解析 “import”, “alias”, “bean”. 标签

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(
解析 “import”, “alias”, “bean”. 标签

	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;
					//判断是否是默认 namespaceUri 是否是 http://www.springframework.org/schema/beans
					if (delegate.isDefaultNamespace(ele)) {
					    //对该节点进行具体解析-按照类型  import、alias、bean、beans
						parseDefaultElement(ele, delegate);
					}
					else {
						//用户自定义了 namespaceUri
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//用户自定义的标签
			delegate.parseCustomElement(root);
		}
	}
DefaultBeanDefinitionDocumentReader.parseDefaultElement(:分类解析标签 import、alias、bean、beans

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(
按照类型解析 import、alias、bean、beans。
进入具体源码后我们可以发现一个规律,以下四种类型的划分不是绝对平等的,因为 import可以引用其他文件,beans可以包含多个bean,

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//解析 import 标签,可以引用其他配置文件
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		// 解析 alias 标签,可以为bean设置不同的别名
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//解析 bean 标签
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		//解析 beans 标签
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

总结

限于篇幅,对于具体 import、alias、bean、beans 标签的具体解析我将再新起一篇文章( Spring源码学习:DefaultBeanDefinitionDocumentReader.parseDefaultElement),但是看完本文基本可以看到Spring在处理配置文件的基本思路了。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页