Spring源码(第二章、2.容器的基本实现)

2.1.核心类介绍

在这里插入图片描述

通过beans工程的结构介绍,我们对beans工程有了初步的认识,在读源码前有必要了解Spring中最核心的连个类。

2.1.1 DefaultListableBeanFactory

XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实实在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口,一下是ConfigurableListableBeanFactory的层次结构图,以及类图。

  • AliasRegistry:定义对alias的简单增删改等操作

  • SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现

  • SingletonBeanRegistry:定义对单利的注册及获取

  • BeanFactory : 定义获取bean及bean的各种属性

  • DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现

  • HierarchicalBeanFactory:继承BeanFactory,也就是在Beanfactory定义的功能基础上增加了对parentFactory的支持。
    在这里插入图片描述
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/99d42919607b493493330a585eba5ec3.pn

  • BeanDefinitionRegisry:定义对BeanDefinition的各种增删改操作

  • FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。

  • ConfigurableBeanFactory:提供配置Factory的各种发发

  • ListableBeanFactory:根据各种条件获取bean的配置清单

  • AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能

  • AutowireCapableBeanFactory:提供创建Bean、自动注入、初始化以及一个工Bean的后处理器。

  • AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AwtowireCapableBeanFactory进行实现。

  • ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。

  • DefaultListableFeanFactory:综合上面所有功能,主要是对Bean注册后的处理。
    XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册以及获取Bean都是使用从父类DefaultListableBeanFactory集成的方法实现,而唯独与父类不通的个性化实现就是增加嘞XmlBeanDefinitionReader类型的reader属性,在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

2.1.2.XmlBeanDefintionReader

Xml配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先我们看看各个类的功能。

  • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Reaource。
  • BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
  • EnvironmentCapablt:定义获取Environment方法。
  • DecoumentLoader :定义从资源文件加载到转换为Document的功能
  • AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
  • DeanDefinitionDocumentReader:定义读取Document并注册BeanDefinition功能。
  • BeanDefinitionParserDelegate:定义解析Element的各种方法。
    经过以上分析,我们可以梳理出整个XML配置文件读取的大致流程,在XmlBeanDifinitionReader中主要包含以下几个步骤。
    在这里插入图片描述

(1)通过继承自AbstractBeanDefinitionReader中的方法,来使永ResourLoader将资源文件路径转换为对应的Reaource文件
(2)通过DocumentLoader对Reaource文件进行转换,将Resource文件转换为Document文件。
(3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使永BeanDefintionParserDelegate对Element进行解析。

2.2 容器的基础XmlBeanFactory

在这里插入图片描述
时序图从test类开始,首先调用改了ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了,当我们有了Eesource后就可以进行XmlBeanFactory的初始化了,那么Resource资源是如何封装的呢?

2.2.1 配置文件封装

Spring的配置文件读取是通过ClassPathResource进行封装的,如 new ClassPathResource(“text.xml”); 那么ClassPathResource完成了什么功能呢?
在java中,讲不通来源的资源抽象成URL,通过注册不同的handler(URLSAtreaHandler)来处理不同来源的读取逻辑,一版Handler的类型使用不通前缀来识别,如:file、http、jar等,然而URL没有默认定义相对ClassPath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀,这需要了解URL的实现机制,而URL也没提供一些基本的方法,如检查当前资源是否存在、检查当前资源是否可读等,因而Spring需要对使用的资源子线自己的抽象结构:Resource接口来封装底层资源。

public interface InputStreamSource{
   
	InputStream getInputStream() throws IOException;
}

public interface Resource extends InputStreamSource{
   
	boolean exists();
	boolean isReadable();
	boolean isOpen();
	URL getUrl() throws IOPxception;
	URI getUri() throws IOPxception;
	File getFile() throws IOPxception;
	long lastModified() throws IOPxception;
	Resource createRelative(String relativePath)throws IOPxception;
	String getFilename();
	String getDescription();
}

InputStreamSource 封装任何能返回InputStream的类,比如File、ClassPath下的资源和Byte Array等,它只有一个方法getInputStream返回一个新的InputStream对象。
Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、ClassPath等,首先,定义了3个判断当前资源状态的方法:存在性-exists、可读性-isReadable、是否可读或者打开状态-isOpen。另外,Resource还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性,获取文件名(getFilename())。为了方便操作,Resource还提供了基于当前资源创建一个相对资源的方法,createRelative()。在错误处理中需要详细地打印出错的资源文件,因而Resource还提供了 getDescription()方法用于在错误处理中的打印信息。
对于不通来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、ClassPath资源(ClassPathResource)、URL资源(URLResource)、InputStream资源(InputStreamResource)、Byte数组(ByteResource)等。

2.2.2 加载Bean

之前提到在XmlBeanFatory构造幻术中调用了XmlBeanDefinitionReader类型的reader属性提供的方法,this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入单,我们先来看看这个方法的时序图,
在这里插入图片描述

从图中我们了解到,绕了半天还是没有切入真正的主题,如加载Xml文档和解析注册Bean,一直在做准备工作,根据时序图一起分析一下这里究竟在准备什么?根据时序图我们梳理过程如下:

  1. 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
  2. 后去输入流,从Resource中过去对应的Inpustream并构造InputSource.
  3. 通过构造InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions
    通过分析代码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
   
		return loadBeanDefinitions(new EncodedResource(resource));
	}

EncodedResource的主要逻辑体现在getReader()方法中,当设置了编码熟属性的时候Spring会使用相应的编码作为输入流的编码。

public Reader getReader() throws IOException {
   
		if (this.charset != null) {
   
			return new InputStreamReader(this.resource.getInputStream(), this.charset);
		}
		else if (this.encoding != null) {
   
			return new InputStreamReader(this.resource.getInputStream(), this.encoding);
		}
		else {
   
			return new InputStreamReader(this.resource.getInputStream());
		}
	}

以上代码构造了一个有编码的InpuStreamReader,构造完后再转入可复用的方法loadBeanDefinitions(new EncodedResource(resource)).
这个方法内部才是真正的数据准备阶段,就是是时序图所描述的逻辑:

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

		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();

		if (!currentResources.add(encodedResource)) {
   
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}

		try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
   
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
   
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
		}
		catch (IOException ex) {
   
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
   
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
   
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

再补充整理一下数据准备阶段的逻辑,首先对传入的人source参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分 doLoadBeanDefinitions(inputSource, encodedResource.getResource())。

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 (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. 获取对XML文件的验证模式
  2. 加载XML文件,并得到对应的document
  3. 根据返回的document注册Bean信息
    这三个步骤支撑着整个Spring容器部分的实现基础,尤其是第三步,对排至文件的解析,逻辑分厂复杂,那我我们先从获取XML文件的验证模式开始讲起

2.3 获取XML的验证模式

了解XML文件的读者都应该知道XML文件的验证模式保证了XML文件的正确性,而比较常用的验证模式有两种:DTD和XSD。它们之间有什么区别呢?

2.3.1 DTD与XSD区别

DTD(Document Type Definition)即文档定义类型,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确有效的方法,可以通过比较XML文档和DTD文件来查看文档是否符合规范,元素和标签是否使用正确。一个DTD文档包含:元素的定义规则,元素之间关系的定义规则,元素可以使用的属性,可使用的尸体或符号规则。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper >
代码

</mapper> 

XML Schema语言就是XSD(XML Schemas Definition),XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema 指定一个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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">


</beans>

DTD和XSD的区别

  1. DTD代表文档类型定义,而XSD代表XML Schema定义。
  2. DTD主要用于定义XML文件的结构,而XSD主要用于描述XML文件的内容和结构。
  3. DTD是不可扩展的,而XSD是可扩展的。
  4. DTD源自SGML(标准通用标记语言)语法,而XSD是用XML编写的。
  5. DTD不支持命名空间,而XSD支持命名空间。
  6. DTD不支持数据类型,而XSD支持元素和属性的数据类型。
  7. DTD对XML的控制较少,而XSD对XML提供了更多的控制。
  8. DTD比XSD更难学习,而XSD很容易学习,因为这里我们不需要学习新语言。

2.3.2 验证模式的读取

了解了DTD与XSD的区别后我们再去分析Spring中对于验证模式的提取就更容易理解了,通过分析我们锁定了Spring通过getValidationModeForResoure方法来获取定影资源的验证模式。

protected int getValidationModeForResource(Resource resource) {
   
		int validationModeToUse = getValidationMode();
		if (validationModeToUse != VALIDATION_AUTO) {
   
			return validationModeToUse;
		}
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
   
			return detectedMode;
		}
		// Hmm, we didn't get a clear indication... Let's assume XSD,
		// since apparently no DTD declaration has been found up until
		// detection stopped (before finding the document's root tag).
		return VALIDATION_XSD;
	}

方法的实现其实是如果设定了验证模式则使用设定的模式验证,否则使用自动检测的方式,而自动检测验证模式的工作委托给了专门处理类,XmlValidationModeDetector的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 {
   
			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);
		}
	}
public int detectValidationMode(InputStream inputStream) throws IOException {
   
		// Peek into the file to look for DOCTYPE.
		BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
		try {
   
			boolean isDtdValidated = false;
			String content;
			while ((content = reader.readLine()) != null) {
   
				content = consumeCommentTokens(content);
				if (this.inComment || !StringUtils.hasText(content)) {
   
					continue;
				}
				if (hasDoctype(content)) {
   
					isDtdValidated = true;
					break;
				}
				if (hasOpeningTag(content)) {
   
					// End of meaningful data...
					break;
				}
			}
			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();
		}
	}

Spring用来加测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD否则就是XSD。

2.4 获取Document

经过了验证模式准备的步骤就可以进行Document加载了,同样XmlBeanFactoryReader类对于文档读取并没有亲力亲为,而是委托给了DocumentLoader去执行,这里的DocumentLoader是个接口,而真正调用的是DefaultDocumentLoader,解析代码如下:

	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
   

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isTraceEnabled()) {
   
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}

这里的代码大致是先创建DocumentBuilderFactory,在通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver函数获取的返回值,代码如下:

protected EntityResolver getEntityResolver() {
   
		if (this.entityResolver == null) {
   
			// Determine default EntityResolver to use.
			ResourceLoader resourceLoader = getResourceLoader();
			if (resourceLoader != null) {
   
				this.entityResolver = new ResourceEntityResolver(resourceLoader);
			}
			else {
   
				this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
			}
		}
		return this.entityResolver;
	}

那么?EntityEesolver到底是做什么用的呢?

2.4.1 EntityEesolver用法

在loadDocument方法中设计一个参数EntityResolver,何为EntityResolver?官网的解释如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的生命,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明DTD的URI地址)来下载相应的DTD声明,并进行认证。下载是个漫长的过程,当网路中断时这里就会报错,这就是因为相应的DTD生命没有被找到的原因。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD问阿金放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。
首先EntityResolver的接口方法声明,
InputSource resolveEntity (String publicId,String systemId)
这里,它接收了两个参数 publicId和systemId,并返回一个inputSource对象。这里我们以特定配置文件来进行讲解。

  1. 如果我们在解析验证模式为XSD的配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    ...
</beans>

读取到一下两个参数。

  • publicId:null
  • systemId:http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  1. 如果我们在解析验证模式为DTD的配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
</beans>

读取到的两个参数

  • publicId : -//mybatis.org//DTD Mapper 3.0//EN
  • systemId:http://mybatis.org/dtd/mybatis-3-mapper.dtd
    之前已经提到过,验证文件默认的加载方式是通过URL进行网路下载获取,这样会造成延迟,用户体验不好,一版的做法都是将验证文件放置在自己的工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?我们以加载DTD文件为例来看看Spring中是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
			throws SAXException, IOException {
   

		if (systemId != null) {
   
			if (systemId.endsWith(DTD_SUFFIX)) {
   
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			else if (systemId.endsWith(XSD_SUFFIX)) {
   
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}

		// Fall back to the parser's default behavior.
		return null;
	}

我们可以看到,对不同的验证模式,Spring使用了不同的解析器解析。这里简单描述一下原理,比如加载DTD类型的BeansDtResolver的resolveEntity是直接截取systemId最后的xx.dtd然后当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemid所对应的XSD文件并加载。

public InputSource resolveEntity(@Nullable 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 && systemId.endsWith(DTD_EXTENSION)) {
   
			int lastPathSeparator = systemId.lastIndexOf('/');
			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 {
   
					Resource resource = new ClassPathResource(dtdFile, getClass());
					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 (FileNotFoundException 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;
	}

2.5 解析及注册BeanDdfinitions

当把文件转换为Document后,接下来的提取及注册bean就是我们的重头戏。继续上面的分析,当程序已经拥有XML文档文件的Document实例对象时,就会引用下面的方法:

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

其中的参数doc是通过上一节loadDocument加载转换出来的。在这个方法中很好地应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumenteader()中完成的,而通过此方法,BeanDefinitionDocumentReader真正的类型其实已经是BeanDefinitionDocumentReader了,进入BeanDefinitionDocumentReader后,发现这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
   
		this.readerContext = readerContext;
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

经过艰难险阻,磕磕绊绊,我们终于到了核心逻辑的底部,doRegisterBeanDefinitions()至少我们在这个方法中看到了希望。
如果说以前一直是XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正的开始解析了,我们期待的核心部分真正的开始了。

	protected void doRegisterBeanDefinitions(Element root) {
   
		
		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)) {
   
			
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				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;
				}
			}
		}
		//解析前处理,留给子类实现
		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		// 解析处理后,留给子类实现
		postProcessXml(root);

		this.delegate = parent;
	}

通过上面的代码我们看到了处理流程,首先是对profile的处理,然后开始进行解析,可是当我们跟进preProcessXml(root)或者postProcessXml(root)发现代码是空的,既然时空的写这个还有什么用呢?就像面向对象设计方法学中常说的一句话,一个类要么是面向继承设计的,要么用final修饰。在BeanDefinitionDocumentReader中并没有用final修饰,所以他是面向继承而设计的,这两个方法正式为子类而设计的,如果读者有了解过设计模式,可以很快的范引出这是模板方法模式,如果继承自BeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。

2.5.1 profile 属性的使用

我们注意到在注册Bean的最开始是对PROFILE_ATTRIMUTE属性的解析,可能对于我们来说,profile属性并不是很常用。让我们先了解一下和这个属性。
分析profile前我们先了解下profile的用法,官方实例代码如下,

在这里插入图片描述
集成到web环境中时,在web.xml中加入一下代码:
在这里插入图片描述

有了这个特性我们就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便进行切换开发、部署环境,最常用的就是更换不同的数据库。
了解了profile的使用再来分析代码会清晰的多,首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile都是符合环境变量中所定义的,不定义则不会浪费性能去解析。

2.5.1 解析并注册BeanDefinition

处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinition(root,this.delegate)

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   
	// 对beans的处理
		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)) {
   
					// 对beans的处理
						parseDefaultElement(ele, delegate);
					}
					else {
   
					// 对beans的处理
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
   
			delegate.parseCustomElement(root);
		}
	}

上面的刀马看起来逻辑还是蛮清晰的,因为在Spring中的XML配置里面有两个大类Bean声明,一个是默认的如:

另一类就是自定义的,如:
tx:annotation-driven/
而两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道改怎么做,但是如果是自定义的,那么就需要用户实现一些接口及配置了,对于跟节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析,而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURL()获取命名空间,并与Spring中固定的命名空间http://www.Springframework.org/schema/beans进行比对。如果一直则认为是默认,否则就认为是自定义。而对于默认的标签解析于自定义标签解析我们将会在下一章进行讨论。

最近更新

  1. TCP协议是安全的吗?

    2024-01-12 21:44:08       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-12 21:44:08       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-12 21:44:08       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-12 21:44:08       20 阅读

热门阅读

  1. QT day5

    QT day5

    2024-01-12 21:44:08      34 阅读
  2. 用python实现把PDF转成图片,测试成功转化代码

    2024-01-12 21:44:08       27 阅读
  3. __declspec(dllexport)与__declspec(dllimport) 的区别

    2024-01-12 21:44:08       32 阅读
  4. C语言学习记录—进阶作业(通讯录静态版本)

    2024-01-12 21:44:08       36 阅读
  5. Netty Channel 详解

    2024-01-12 21:44:08       37 阅读
  6. 代码随想录算法训练营Day24|77. 组合

    2024-01-12 21:44:08       38 阅读
  7. ClickHouse中JOIN算法选择逻辑以及auto选项

    2024-01-12 21:44:08       30 阅读
  8. python之异常与日志

    2024-01-12 21:44:08       37 阅读
  9. 职工工作量统计(课程设计)

    2024-01-12 21:44:08       32 阅读
  10. Go语言中Print Printf Println的区别

    2024-01-12 21:44:08       33 阅读
  11. SQL语句

    SQL语句

    2024-01-12 21:44:08      35 阅读
  12. 在vue中使用v-for遍历arco.design图标

    2024-01-12 21:44:08       35 阅读
  13. ant-design-vue的table组件的自定义表头和表格内容

    2024-01-12 21:44:08       37 阅读