Spring源码阅读 二

容器的基本实现(一)

容器的基本用法
一般来说实例化一个Bean的配置基本是这样子的的
在这里插入图片描述在这里插入图片描述

注意:这里只贴出了几个主要的类,还有他的接口实现没有贴出来

上面这些代码其实给我们完成了以下几种功能

  1. 读取配置文件spring.xml
  2. 根据spring.xml 中配置找到对应的类的配置,并且实例化
  3. 调用实例化后面的实例

Spring结构组成

beans包的层级结构
在这里插入图片描述
核心类介绍

1. DefaultListableBeanFactory
(package org.springframework.beans.factory.support)
DefaultListableBeanFactory是整个Bean加载的核心部分,是Spring注册和加载bean的核心实现,DefaultListableBeanFactory继承AbstractAutowireCapableBeanFactory 并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口,我们可以从iead导出他的类结构图,如下
在这里插入图片描述下面我对每一个类做一个大致的介绍:
AliasRegistry:定义对alias的简单的增删改查等等操作
SimpleAliasRegistry: 主要使用map做为alias缓存,并且对接口AliasRegistry进行实现
SingletonBeanRegistry:定义对单例的注册以及获取
BeanFactory:定义获取Bean以及各种Bean的属性
DefaultSingletonBeanRegisty:对接口SingletonBeanRegistry各个函数的实现
HierarchicalBeanFactory:继承了BeanFactory,也就是在BeanFactory定义的功能上面增加了对parentFactory的支持
BeanDefinitionRegistry:定义了对BeanDefiniton的各种增删改操作
FactoryBeanRegistrySupport:在DefaultSingleBeanRegister基础上面增加了对FactoryBean的特殊处理
configurableBeanFactory:提供配置Factory的各种方法
ListableBeanFactory:根据各种条件获取bean的配置清单
AbstarctBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能
AutowireCapableBeanFactory:提供了创建bean,自动注入,初始化,以及应用bean的后处理器
AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现
configurableListableBeanFactory:BeanFactory配置清单,指定忽略类型以及接口等等
DefaultListableBeanFactory:综上述功能,主要是对Bean注册后的处理

2. XmlBeanDefinitionReader
(package org.springframework.beans.factory.xml)
xml配置文件的读取是Spring的重要功能,S大部分是以配置为切入点的

ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
BeanDefinitionReader:主要定义资源文件的读取并转换为BeanDefinition的各个功能
Environmentcapable:定义获取Environment方法
DocumnetLoader:定义从资源文件加载到转换为Document的功能
AbstractBeanDefinitionReader:对EnvironmentCapable,BeanDefinitionReader类定义功能的实现
BeanDefinitionParserDelegate:定义解析Element的方法
由此我们可以推断出spring读取xml配置文件的流程

  • (一)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转为对应的Resource文件

  • (二)通过DocumentLoader对Resource文件解析转换,将Resource文件转换为Documnet文件

  • (三)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParseDelegate对Element进行解析

容器的基础 xmlBeanFactory( 二)

注意: 我构建的spring5的环境,这里xmlBeanFactory 在spring3.0就已经弃用所以不能使用
BeanFactory ctx = new XmlBeanFactory(new ClassPathResource(“spring.xml”));
但是我们可以用 ClassPathXmlApplication读取相关的Bean
最后我是基于Spring源码深度解析这本书来学校spring源码的,所以在这里还是研究一下xmlBeanFacctory,后面在介绍其他替代方法

首先看一下时序图
在这里插入图片描述我们可以看到在测试类中首先调用了classPathResource的构造函数来构造Resource资源文件的实例对象,然后后续资源处理就可以用Resource提供的各种服务来操作,但是Resource资源是如何进行封装的

配置文件的封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource(“spring.xml”)
在java中,将不同的来源的资源抽象为一个url,通过注册不同的handler来处理不同来源的资源读取逻辑,一般的handler的类型使用不同的前缀来表示,
Spring对于资源的操作(检查当前资源是否存在,当前资源是否可读。。。)提供了Resource接口
在这里插入图片描述抽象了所有的Spring内部所用到的底层资源,File,URL,ClassPath等等,首先定义了3个判断当前资源状态的方法**,存在性,可读性,是否处于打开状态**,另外Resource还提供了不同资源到URL,URI类型的转换,已经获取lastModified属性,文件名,为了方便操作Resource还提供了基于当前资源创建的一个相对方法,createRelative(),getDescription()方法在错误处处理打印信息
在这里插入图片描述由此图可以看出当调用super父类函数进行初始化之后,就开始调用xmlBeanDefinitionReader进行资源加载了,我们首先看他的构造函数
在这里插入图片描述我们一直跟踪代码从(XmlBeanFactory →DefaultListableBeanFactory→AbstractAutowireCapableBeanFactory)到AbstractAutowireCapableBeanFactory中,发现最后调用的是这样一段代码
在这里插入图片描述

其中里面的忽略给的接口的自动装配功能大体来说是这样子的的
当A有属性B,那么在当Spring在获取A的Bean的时候如果他的属性还没有初始化,那么Spring会自动初始化,但是也有情况,B不能被初始化,比如B实现了BeanNameAware接口,Spring是这样子介绍的,自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者通过ApplicationContext通过ApplicationContextAware进行注入

我们看一下ignoreDependencyInterface方法,首先他分别传入了几个不同的(接口)

1 BeanNameAware
如果某个bean需要访问配置文件中本身bean的id属性,这个bean类通过实现BeanNameAware接口,在依赖关系确定之后,初始化方法之前,提供回调自身的能力,从而获得本身bean的id属性,该接口提供了 void setBeanName(String name)方法,需要指出的时该方法的name参数就是该bean的id属性。回调该setBeanName方法可以让bean获取自身的id属性

2.BeanFactoryAware
实现了BeanFactoryAware接口的bean,可以直接通过beanfactory来访问spring的容器,当该bean被容器创建之后,会有一个相应的beanfactory的实例引用。该 接口有一个方法void setBeanFactory(BeanFactory beanFactory)方法通过这个方法的参数创建它的BeanFactory实例,实现了BeanFactoryAware接口,就可以让Bean拥有访问Spring容器的能力

3.BeanClassLoaderAware
spring会把加载业务bean类时使用的类加载器暴露出来

加载Bean
既然*this.reader.loadBeanDefinitions(resource)*是资源最终加载的代码,那么我们来看一下他的时序图
在这里插入图片描述从图中可以看出,整个处理过程无非就是以下几个

  1. 封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource进行封装
  2. 获取输入流
  3. 通过构造的InputSource实例和Resource实例继续调用函数doLoadDefinitions

X m l B e a n D e f i n i t i o n R e a d e r \color{red}{XmlBeanDefinitionReader} XmlBeanDefinitionReader

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

👇

 (XmlBeanDefinitionReader.java)
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);
		}
       // resourcesCurrentlyBeingLoaded是一个ThreadLocal,里面存放Resource包装类的set集合
		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		//如果set中已有这个元素则返回false,进入该条件抛出异常
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
			// 注意InputSource并不来自于spring 它来自于org.xml.sax
				InputSource inputSource = new InputSource(inputStream);
				// 设置编码
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				// 真正加载资源文件的方法  1
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			// 资源加载完毕,移除该Resource
			currentResources.remove(encodedResource);
			// 如果集合为空,移除
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

我们继续进入到doLoadBeanDefinitions之中

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

		try {
			//根据不同的xml约束(dtd,xsd等),将xml文件生成对应的文档对象
			//1.这个方法里面涉及xml的解析
			Document doc = doLoadDocument(inputSource, resource);
			//注册BeanDefinitions
			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);
		}
	}

doLoadBeanDefinitions分为三个部分即

  1. 获取xml的验证方式
  2. 加载xml文件,并转为对应发Document
  3. 根据Document返回对应的Bean的注册信息

下面我们一步一步的来

获取XML的验证方式

首先我们应该xml文件的验证保证了xml的准确性,而xml文件验证比较常用的模式有两种DTD和XSD

DTD和XSD的区别
DTD(Documnet Type Definition)属于文档类型定义,是一种XML约束模式语言,是XML文件的一种验证机制,属于XML文件的组成部分
DTD文档包含:元素的定义,元素间关系的定义规则,元素可使用属性,可使用的实体或者是符合规则
使用DTD需要在头部声明,如果Spring中使用DTD, 不过 现 在 基 本 已 被 X S D 文 档 取 代 \color{red}{现在基本已被XSD文档取代} XSD

在这里插入图片描述XSD
XML Schema语言就是XSD(XML Schemas Definition),它描述了XML文档的结构,可以用一个指定的XML Schema来验证某一个XML文档,用来检查这个XML文档是否符合规范
在使用XML Schema文档对XML实例进行检验时,除了要声明命名空间外,还必须指定这个命名空间对应的XML Schema文档的存储位置
通schemaLoaction属性指定命名空间对应的xml Schema文件包含两个部分:
(一)名称空间的URL
(二)该命名空间所包含的XML Schema的文件位置或URL地址
在这里插入图片描述

验证模式的读取

上面我们简单的介绍了一下xsd和dtd,下面我们看一下Spring的验证模式是怎么读取的
在这里插入图片描述进入doLoadDocument
在这里插入图片描述继续点击getValidationModeForResource
这里的方法调用顺序为(都在XmlBeanDefinitionReader这个类里面调用)
l o a d B e a n D e f i n i t i o n s → d o L o a d B e a n D e f i n i t i o n s → d o L o a d D o c u m e n t → g e t V a l i d a t i o n M o d e F o r R e s o u r c e \color{red}{loadBeanDefinitions→doLoadBeanDefinitions→doLoadDocument→getValidationModeForResource} loadBeanDefinitionsdoLoadBeanDefinitionsdoLoadDocumentgetValidationModeForResource

 (XmlBeanDefinitionReader.java)
    /*
	 * 通过给定Resource给出验证模式。如果没有明确配置验证模式,那么调用detectValidationMode方法去         检测,可以通过xmlBeanDefintionReader.setValidationMode来进行设定
	 */
	protected int getValidationModeForResource(Resource resource) {
		// 默认自动验证 1
		int validationModeToUse = getValidationMode();
		// 如果手动指定验证模式则使用指定的验证模式
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
		//如果有给出具体验证方式,则使用自动检测返回结果
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		// 如果实在不能判断验证模式是那种就使用XSD方式,
		// 因为检测完后还是没有发现DTD模式的声明(在查找document的根标签之前)。
		// 值为3
		return VALIDATION_XSD;
	}

因为自动检测模式是在函数detectValidationMode方法里面实现的,我们进入detectValidationMode
在这里插入图片描述`

 (XmlBeanDefinitionReader.java)
     //检测执行xml文件时该用哪种验证方式,这个xml由Resource对象提供
	//  如果这个文件有DOCTYPE声明,那么就用DTD验证,否则就假定使用XSD。
	// 如果你想要自定义自动验证模式的解决方式,你可以覆盖这个方法

	protected int detectValidationMode(Resource resource) {
		// 默认false
		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);
		}
	}

在这里插入图片描述
由上面的代码可以看出来xmlBeanDefintionReader的detectValidationMode函数又调用了XmlValidationModeDetector的detectValidationMode
这里的方法调用顺序为(ps:[…]这里理解为上面的加上上面的调用是他的整体调用流程)
[ . . . ] → d e t e c t V a l i d a t i o n M o d e ( 类 : X m l B e a n D e f i n i t i o n R e a d e r ) → d e t e c t V a l i d a t i o n M o d e ( 类 : X m l V a l i d a t i o n M o d e D e t e c t o r ) \color{red}{[...]→detectValidationMode(类:XmlBeanDefinitionReader)→detectValidationMode(类:XmlValidationModeDetector)} [...]detectValidationModeXmlBeanDefinitionReaderdetectValidationMode(XmlValidationModeDetector)

 ( XmlValidationModeDetector.java)
 
	public int detectValidationMode(InputStream inputStream) throws IOException {
		// 查看文件以寻找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;
				}
				//有DOCTYPE声明,就跳出去
				if (hasDoctype(content)) {
					isDtdValidated = true;
					break;
				}
				//注释不能进去。开头是"<",后面第一个字符是字母,就进入。
				//比如'<beans xmlns="http://www.springframework.org/schema/beans"'
				//进去后跳出循环
				if (hasOpeningTag(content)) {
					// End of meaningful data...
					break;
				}
			}
			//当遍历到名称空间了也就是"<beans xmlns=...>"还没有DOCTYPE声明,
			//那么就判定他为XSD验证
			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();
		}
	}

我们可以看到consumeCommentTokens

@Nullable
	private String consumeCommentTokens(String line) {
		//  首先如果没有带<!-- 或 -->的直接返回内容代表没有再注释里面
		int indexOfStartComment = line.indexOf(START_COMMENT);
		if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) {
			return line;
		}

		String result = "";
		String currLine = line;
		if (indexOfStartComment >= 0) {
			result = line.substring(0, indexOfStartComment);
			currLine = line.substring(indexOfStartComment);
		}

		while ((currLine = consume(currLine)) != null) {
			//没有在注释中或者不是由注释开头的返回内容,inComment的这个标识位表示当前是否在注释中
			if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) {
				return result + currLine;
			}
		}
		return null;
	}

至此xmL的验证已经完成,下一章我继续总结Spring是如何获取Domcument的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值