不看后悔系列-Spring源码(六):IOC的资源文件定位、解析和BeanDefinition装载、注册

在Spring中关于资源文件的解析和BeanDefinition装载、注册是这个方法,目的就是把BeanDefinition放到beanFactory中

/**
	 * Load bean definitions into the given bean factory, typically through
	 * delegating to one or more bean definition readers.
	 * @param beanFactory the bean factory to load bean definitions into
	 * @throws BeansException if parsing of the bean definitions failed
	 * @throws IOException if loading of bean definition files failed
	 * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 */
	 
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
			throws BeansException, IOException;

此方法定义在AbstractRefreshableApplicationContext,关于这个方法的继承体系跳回去自己看图(容器类图结构):不看后悔系列-Spring源码(二):IOC阅读导航篇

这里我们主要以XmlWebApplicationContext的实现往下看

/**
	 * Loads the bean definitions via an XmlBeanDefinitionReader.
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 * @see #initBeanDefinitionReader
	 * @see #loadBeanDefinitions
	 */
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);   

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

从代码可以看出来,这一段代码就是创建了一个XmlBeanDefinitionReader 对象,然后调用loadBeanDefinitions(beanDefinitionReader);

先看一下XmlBeanDefinitionReader 的继承类图
在这里插入图片描述

BeanDefinitionReader: 主要定义资源文件读取并转换为 BeanDefinition 的各个功能。
EnvironmentCapable: 定义获取 Environment 方法。
DocumentLoader: 定义从资源文件加载到转换为 Document 的功能。
AbstractBeanDefinitionReader: 对 EnvironmentCapable 、BeanDefinitionReader 类定义的功能进行实现。

跟着 loadBeanDefinitions(beanDefinitionReader),我们继续往下看

/**
 * Load the bean definitions with the given XmlBeanDefinitionReader.
 * <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method;
 * therefore this method is just supposed to load and/or register bean definitions.
 * <p>Delegates to a ResourcePatternResolver for resolving location patterns
 * into Resource instances.
 * @throws IOException if the required XML document isn't found
 * @see #refreshBeanFactory
 * @see #getConfigLocations
 * @see #getResources
 * @see #getResourcePatternResolver
 */
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	Resource[] configResources = getConfigResources();  //获取资源文件
	if (configResources != null) {
		reader.loadBeanDefinitions(configResources);
	}
	String[] configLocations = getConfigLocations();    //获取资源文件
	if (configLocations != null) {
		reader.loadBeanDefinitions(configLocations);   
	}
}

这里插个眼(是梦开始的地方,后面会有方法从这里显示的两个调用方法递归)

看官们可以看到,这里有一个判断,根据获取到的类型不一样(Resource和String)来执行不同的策略。这里的Resource和configLocations 都是在初始化的时候定义的

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml");

看过Resource和ResourceLoader的应该知道,Spring是将资源文件抽象成Resource来处理的,所以针对String[] configLocations这种类型的资源文件,其实还没有进行资源文件的定位,所以需要先定位到资源文件,具体各位可以看看里面的处理,最终其实都是跳转到reader.loadBeanDefinitions(configResources);这个方法里的。
于此便完成了IOC第一阶段的第一个步骤(资源文件的定位)

跟着reader.loadBeanDefinitions(configResources);方法

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

这里有一个counter是干嘛的?
提前告诉你count统计的是总的BeanDefiniton数目,(针对本次提供的resources)。

往下跟

/**
* Load bean definitions from the specified resource.
 * @param resource the resource descriptor
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

此方法定义在BeanDefinitionReader中,就是我们刚刚说的XmlBeanDefinitionReader 的父接口。因为我们用的XmlBeanDefinitionReader类,所以此方法的实现在XmlBeanDefinitionReader中,跟下去

/**
 * 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包装成EncodedResource,这里的EncodedResource 为 Resource添加了编码方式和字符集两个属性。

/**
* Load bean definitions from the specified XML file.
 * @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();    //获取已经解析完成的resource
	
    //如果为null,则初始化 resourcesCurrentlyBeingLoaded
	if (currentResources == null) { 
		currentResources = new HashSet<EncodedResource>(4);
		this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	
	//将encodedResource(方法参数)添加到集合中
	if (!currentResources.add(encodedResource)) {
		throw new BeanDefinitionStoreException(
				"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
	   //获取encodedResource的输入流
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
        //包装成inputSource 
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
	    //可以在此设置编码方式
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			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();
		}
	}
}

这个方法看起来很长,其实是虚胖,真正做事的也就doLoadBeanDefinitions(inputSource, encodedResource.getResource())这个方法(看源码莫慌啊,仔细看还是能懂的!)

跟进doLoadBeanDefinitions(inputSource, encodedResource.getResource())之后

/**
	 * 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 {
			Document doc = doLoadDocument(inputSource, resource);   //关键点1
			return registerBeanDefinitions(doc, resource);          //关键点2
		}
		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);
		}
	}

到这里,就到了整个过程的核心。
看官看下整个方法其实也就关键点1、2

Document doc = doLoadDocument(inputSource, resource);   //根据 xml 文件,获取 Document 实例
return registerBeanDefinitions(doc, resource);			//根据 Document 实例,加载注册 BeanDefinition

那么接下来我们先分析关键点1

  • 关键点1
/**
 * 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());
}

在这个核心逻辑方法中,主要做了两件事
(1)调用 getValidationModeForResource() 获取 xml 文件的验证模式
(2)调用 loadDocument() 根据 xml 文件获取相应的 Document 实例

这块的内容涉及到了有关xml文件知识,各位看官要去补补课了(菜鸟教程XML教程),主要了解一下
(1)XML文件的解析方式(DOM、SAX【Spring采用的解析方式】、StAX)
(2)XML文件命名空间
(3)XML文件的验证模型(DTD、XSD)

因为感觉这块别人比我自己写要好,所以直接就引用了。qaq
关于获取验证模型方法getValidationModeForResource(resource),的解析各位看官可以参考
死磕Spring-IOC 之 获取验证模型

这里直接写个结论

主要是通过读取 XML 文件的内容,判断内容中是否包含有 DOCTYPE ,如果是 则为 DTD,否则为 XSD,当然只会读取到 第一个 “<” 处,因为 验证模式一定会在第一个 “<” 之前。如果当中出现了 CharConversionException 异常,则为 XSD模式

关于获取document对象方法的解析各位看官可以参考
死磕Spring-IOC 之 获取 Document 对象

  • 关键点2

registerBeanDefinitions(doc, resource); 这个方法主要完成了三件事
(1)资源文件的解析
(2)BeanDefinition装载
(3)BeanDefinition注册

我们看下它的代码

/**
* Register the bean definitions contained in the given DOM document.
 * Called by {@code loadBeanDefinitions}.
 * <p>Creates a new instance of the parser class and invokes
 * {@code registerBeanDefinitions} on it.
 * @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;
}

(1)创建一个BeanDefinitionDocumentReader
(2)获得注册前的BeanDefinition数量
(3)documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 资源文件解析,装载注册BeanDefinition
(4)返回本次注册BeanDefinition数量

注意!!!
这里有一个createReaderContext(resource)方法,如果我在这里讲你肯定不会明白的,所以我们现在这里插个眼,然后后面讲到bean标签解析的时候会再说这个方法。这里先和你说它的作用是注册事件监听器。

先看下createBeanDefinitionDocumentReader()

private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

/**
 * Create the {@link BeanDefinitionDocumentReader} to use for actually
 * reading bean definitions from an XML document.
 * <p>The default implementation instantiates the specified "documentReaderClass".
 * @see #setDocumentReaderClass
 */
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
	return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}

利用反射创建DefaultBeanDefinitionDocumentReader对象
这个DefaultBeanDefinitionDocumentReader是用来解析document的类
看下它的类图
在这里插入图片描述

然后看下documentReader.registerBeanDefinitions(doc, createReaderContext(resource));方法

/**
 * 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 对象,以及解析器的当前上下文,包括目标注册表和被解析的资源。
看下DefaultBeanDefinitionDocumentReader的实现

/**
 * 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;
	logger.debug("Loading bean definitions");
	Element root = doc.getDocumentElement();
	doRegisterBeanDefinitions(root);
}
/**
* 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);  //创建BeanDefinitionParserDelegate对象(这是一个BeanDefinition辅助解析类)

	if (this.delegate.isDefaultNamespace(root)) {
		//处理profile
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);     
		if (StringUtils.hasText(profileSpec)) {
			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;
}
/**
* 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) {
	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);
	}
}

这块代码进入到了资源文件的解析和BeanDefinition装载,主要内容为如果根节点或者子节点采用默认命名空间的话,则调用 parseDefaultElement() 进行默认标签解析,否则调用 delegate.parseCustomElement() 方法进行自定义解析

由于篇幅原因,关于后续内容讲解请跳转:不看后悔系列-Spring源码(七):标签解析——BeanDefinition装载、注册,IOC第一阶段完结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值