Spring源码阅读 三

获取Domcument

在 XmlBeanDefinitionReader.doLoadDocument() 方法中做了两件事情,一是调用 getValidationModeForResource() 获取 XML 的验证模式,二是调用 DocumentLoader.loadDocument() 获取 Document 对象,上文我们获取了xml文件的验证方式 ( 传送门 ),接下来我们看一下获取domcument

XmlBeanDefinitionReader.java
/
	 * 使用配置好的DocumentLoader文档加载器加载指定的文档
	 * documentLoader 默认实现DefaultDocumentLoader
	 */
	protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
		return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
				getValidationModeForResource(resource), isNamespaceAware());
	}

其中documentLoader 默认实现DefaultDocumentLoader,点击进查看他的代码

DefaultDocumentLoader.java
/**
	 * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
	 * XML parser.
	 * 使用标准JAXP配置XML解析器加载InputSource的Document对象
	 * namespaceAware 默认命名为false
	 * validationMode 上面getValidationModeForResource返回过来的类型
	 */
	@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
         //创建文档构建器工厂对象,并初始化一些属性
		//如果验证模式为XSD,那么强制支持XML名称空间,并加上schema属性
		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		// 判断记录器Trace跟踪是否激活。Trace跟踪激活后会打印比较详细的信息。有助于提高系统性能
		if (logger.isTraceEnabled()) {
			logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		// 创建一个JAXP文档构建器
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		// 按照XML文档解析给定inputSource的内容,然后返回一个新的DOM对象

		return builder.parse(inputSource);
	}

由上可以看出Spring在这里执行步骤是
1,建立DocumentBuildFactory**(属于javax.xml.parsers)**
2,通过DocumentBuildFactory创建DocumentBuilder
3,解析inputSource来返回Document对象

我们看一下createDocumentBuilderFactory

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
			throws ParserConfigurationException {

		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(namespaceAware);
// 如果没有指令应用禁止验证, VALIDATION_NONE=0
		if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
			factory.setValidating(true);
			// VALIDATION_XSD=3
			if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
				// 使用xsd命名空间
				factory.setNamespaceAware(true);
				try {
					// 设置schema属性
					factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
				}
				catch (IllegalArgumentException ex) {
					ParserConfigurationException pcex = new ParserConfigurationException(
							"Unable to validate using XSD: Your JAXP provider [" + factory +
							"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
							"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
					pcex.initCause(ex);
					throw pcex;
				}
			}
		}

		return factory;
	}

这里其实使用SAX解析XML的,使用SAX我们就不得不说一下EntityResolver,EntityResolve是org.xml.sax下面的一个接口,官方定义如果SAX应用实现自定义处理外部实体,则必须实现这个接口,并且用setEntityResolver向SAX驱动器注册一个实例,EntityResolver 的作用就是项目本身就可以提供一个如何寻找DTD 的声明方法,在Spring中的参数entityResolver是这样获取的
在这里插入图片描述

	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;
	}
EntityResolver 用法

在这里插入图片描述
EntityResolver接受两个参数,var1(pubilcId),var2(ststemId),返回一个inputSource对象
假如我们读取验证模式为XSD的配置文件
读取到两个参数

  1. pubilcId:null
  2. ststemId:http://www.springframework.org/schema/beans/spring-beans.xsd
    如果解析DTD的文件的话,读取到的参数为
  3. pubilcId:-//Spring//DTD BEAN 2.0//EN
  4. ststemId:http://www.springframework.org/dtd/spring-beans-2.0.dtd

由于验证文件的默认加载方式是通过url解析网络下载获取,这样会造成延迟,用户体验也不好,一般我们都会放到我们自己的工程里,那么怎么才可以可以将url转为从我们的工程中的对应文件了,我们这里来看spring是怎么实现的
spring中一般使用DelegatingEntityResolver类为EntityResolver的实现类
在这里插入图片描述
DelegatingEntityResolver实现了EntityResolver,DelegatingEntityResolver实现的resolveEntity方法是只有这样的


	@Override
	@Nullable
	public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId)
			throws SAXException, IOException {

		if (systemId != null) {
			// 如果是dtd DTD_SUFFIX=.dtd
			if (systemId.endsWith(DTD_SUFFIX)) {
				return this.dtdResolver.resolveEntity(publicId, systemId);
			}
			// XSD_SUFFIX = .xsd
			else if (systemId.endsWith(XSD_SUFFIX)) {
				return this.schemaResolver.resolveEntity(publicId, systemId);
			}
		}

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

对应不同的验证模式,spring使用了不同的解析器解析,比如:

  1. 加载DTD类型的BeanDtdResolver一般是直接截取systemId最后的xx.dtd然后去当前路径下面找
  2. 加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/spring.schemas我家中找到systemid所对应的xsd文件并进行加载

解析注册BeanDefinitions

继续上面的代码,解析注册BeanDefinitions其实是在**documentReader.registerBeanDefinitions(doc,createReaderContext(resource))**方法里面的,也就是图中红线部分在这里插入图片描述点击进去我们看一下他的源码

XmlBeanDefinitionReader.java
	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		// 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader,
		// 目的是为了对xml格式的BeanDefinitions进行解析
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		// getRegistry()方法拿的是bean工厂对象,beanDefinition注册在工厂中
		//这个方法就是返回已经被注册在工厂中的beanDefinitions数量
		int countBefore = getRegistry().getBeanDefinitionCount();
        // 具体的解析过程由DefaultBeanDefinitionDocumentReader完成
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		// 返回上个方法真正注册在工厂中的beanDefinition数量
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

注意这里documentReader.registerBeanDefinitions(doc,
createReaderContext(resource));这使用了委派模式,关于设计模式可以关注我的java设计模式系列,一直不断更新中,

我们先来看一下createBeanDefinitionDocumentReader()方法以及documentReaderClass
在这里插入图片描述
在这里插入图片描述也就是说BeanDefinitionDocumentReader是一个接口,registerBeanDefinitions(doc, createReaderContext(resource)),实现的其实是他的子类,点击进去registerBeanDefinitions进去

DefaultBeanDefinitionDocumentReader.java
	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		//入参时创建的XmlReaderContext对象
		this.readerContext = readerContext;
        //传进xml文档对象的根元素
		doRegisterBeanDefinitions(doc.getDocumentElement());
	}

由上面可以知道这段代码的重要目的之一就是提取root,以便于再次将xml的文档根元素作为参数传递,具体的注册其实是通过**doRegisterBeanDefinitions(doc.getDocumentElement());**实现的

我们的XML加载解析的准备阶段已经全部完成,下面就是开始进行解析了

还是老规矩点击进doRegisterBeanDefinitions这个方法

protected void doRegisterBeanDefinitions(Element root) {
		// 任何被嵌套的<beans>元素都会导致此方法的递归。为了正确的传播和保存<beans>的默认属性、
		// 保持当前(父)代理的跟踪,它可能为null
		// 为了能够回退,新的(子)代理具有父的引用,最终会重置this.delegate回到它的初始(父)引用。
		// 这个行为模拟了一堆代理,但实际上并不需要一个代理

        // BeanDefinitionParserDelegate中定义了一系列具体的spring xml文件定义的各种元素
		// 这里实现的是DefaultBeanDefinitionDocumentReader
		BeanDefinitionParserDelegate parent = this.delegate;
		// 创建一个代理 初始化
		this.delegate = createDelegate(getReaderContext(), root, parent);

		//如果默认名称空间是"http://www.springframework.org/schema/beans"
		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			//存在xml文件设置"profile" 运行时环境
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				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;
				}
			}
		}
       //xml预处理,子类没有重写里面就是空实现
		preProcessXml(root);
		//2.2生成BeanDefinition,并注册在工厂中
		parseBeanDefinitions(root, this.delegate);
		//xml后处理,子类没有重写里面就是空实现
		postProcessXml(root);

		this.delegate = parent;
	}

我们按照代码执行顺序进入方法一个个的查看,先是createDelegate,这主要是对delegate(DefaultBeanDefinitionDocumentReader进行一个初始化)
在这里插入图片描述在继续往下走,进入if判断,这里我们可以看到spring从xml根元素中获取了一个名字叫PROFILE_ATTRIBUTE,值是profile的属性值,我们不着急往下走,先看看这个profile是什么

Profile属性

Spring中的Profile功能其实早在Spring 3.1的版本就已经出来,它可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中,就好像我们的运行环境差不多把,通常我们的开发会部署多套环境,如测试环境,开发环境,生产环境,每一套环境对应不同的配置文件,Profile使我们更加灵活的在不同的配置环境之间进行切换,最常用的应该就是切换不同的数据库把
具体使用

在这里插入图片描述web.xml 加入以下代码

<context-param>

    <param-name>spring.profiles.default</param-name>

    <param-value>development</param-value>

</context-param>

回到主题,继续查看doRegisterBeanDefinitions的代码,我们看到preProcessXml(root)和postProcessXml(root)这两个方法,这两个方法都是同一个类(DefaultBeanDefinitionDocumentReader.jva)里面的
在这里插入图片描述在这里插入图片描述不难看出这两个都是空方法,具体的实现是留给子类去实现的,这里使用到了设计模式里的模板方法,我们直接去看parseBeanDefinitions,这个方法里面主要使用了Spring的Bean规则从文档根元素对文档进行解析

DefaultBeanDefinitionDocumentReader
		protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		//spring定义了元素默认名称空间是"http://www.springframework.org/schema/beans"
		//进入条件
		if (delegate.isDefaultNamespace(root)) {
			//获取根元素下的子Node,注意,Node不一定是子标签,可能是回车,可能是注释
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					//拿到了<beans>下的子标签
					Element ele = (Element) node;
					//3.如果该标签属于beans的名称空间,则进入这个方法
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						//4.如果该标签属于其他的名称空间比如:context,aop等
						//xmlns:aop="http://www.springframework.org/schema/aop"
						//xmlns:context="http://www.springframework.org/schema/context"
                        // 即如果没有使用默认命名空间,则使用用户自定义的配置规则
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			// 文档的根节点没有使用Spring的默认命名空间
			// 使用自定义的解析规则解析文档根目录
			delegate.parseCustomElement(root);
		}
	}

对于上述代码,我们可以知道spring解析俩种不同类型的bean,即自定义bean(使用delegate.parseCustomElement)和默认的bean(使用parseDefaultElement),主要通过判断node.getNamespaceURI()获取命名空间,并与spring给定的命名空间进行对比

下 面 一 章 我 们 分 别 看 看 这 俩 种 标 签 对 于 s p r i n g 来 说 是 怎 么 解 析 并 实 现 的 {下面一章我们分别看看这俩种标签对于spring来说是怎么解析并实现的} spring

希 望 可 以 和 大 家 一 起 学 习 , 一 起 进 步 , 哈 哈 哈 哈 \color{red}{希望可以和大家一起学习,一起进步,哈哈哈哈}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值