《Spring源码深度分析》第3章 默认标签的解析

前言

汇总:《Spring源码深度分析》持续更新中…

之前提到过 Spring 中的标签包括默认标签自定义标签两种,而两种标签的用法以及解析方式存在着很大的不同,本章节重点带领读者详细分析默认标签的解析过程。

一、Spring默认的四个标签

四个默认标签:

  1. beans
  2. import
  3. alias
  4. bean

二、bean标签的解析及注册

在4 种标签的解析中,对 bean 标签的解析最为复杂也最为重要,所以我们从此标签开始深入分析,如果能理解此标签的解析过程,其他标签的解析自然会迎刃而解。

1、BeanDefinition下的三个实现类

BeanDefinition 是一个接口,在 Spring 中存在三种实现:RootBeanDefinition、 ChildBeanDefinition 以及 GenericBeanDefinition。三种实现均继承 了 AbstractBeanDefiniton。

其中BeanDefinition 是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有class、 scope、 lazy-init 等配置属性,BeanDefinition 则提供了相应的 beanClass、 scope、 lazyInit属性,BeanDefinition 和<bean>中的属性是一一对应的。其中 RootBeanDefinition 是最常用的实现类,它对应一般性的< bean>元素标签,GenericBeanDefinition 是自2.5 版本以后新加人的 bean文件配置属性定义类,是一站式服务类。

在配置文件中可以定义父< bean>和子<bean>,父用 RootBeanDefinition 表示,而子< bean>用 ChildBeanDefiniton 表示,而没有父< bean>的< bean>就使用 RootBeanDefinition 表示。
AbstractBeanDefinition 对两者共同的类信息进行抽象

Spring 通过 BeanDefinition 将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些 BeanDefiniton 注册到 BeanDefinitonRegistry 中。Spring 容器的 BeanDefinitionRegistry 就像是 Spring 配置信息的内存数据库,主要是以 map 的形式保存,后续操作直接从 BeanDefinitionRegistry 中该取配置信息。它们之间的关系如图 3-2 所示。
在这里插入图片描述

2、解析BeanDefinition

1.processBeanDefinition

首先我们进入函数 processBeanDefinition(ele, delegate)。

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		// 1、(重要)解析xml文件中的bean标签。把值赋予给BeanDefinitionHolder中的beanDefinition、beanName、aliases。
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			// 2、解析默认标签的自定义标签元素(BeanDefinition进行装饰)。场景:在默认的标签元素下,但是其中的'子元素标签'却使用了自定义的配置时,这句代码便会起作用了。
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// 3、Register the final decorated instance.
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// 4、发出响应事件,通知相关监听器,这个bean已经注册完成。这里的实现只为扩展,当程序开发人员需要对注册 BeanDefinition 事件进行监听时可以通过注册监听器的方式并将处理逻辑写人监听器中,目前在 Spring 中并没有对此事件做任何逻辑处理。
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

2.parseBeanDefinitionElement

我们先来分析下:processBeanDefinition方法中的:BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法:

	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		// 对id属性进行解析
		String id = ele.getAttribute(ID_ATTRIBUTE);
		// 对name属性进行解析
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		// 分割name属性,获取别名信息
		List<String> aliases = new ArrayList<>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		// 将id设置成beanName
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			if (logger.isTraceEnabled()) {
				logger.trace("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}

		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}

		// 解析其他的属性(class、parent、singleton...) && child标签(lookup-method、replace-method、construct-arg、property、qualifier...) 并一致封装至GenericBeanDefinition类型实例中
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
					// 如果不存在beanName,那么会根据spring默认的生成规则为该bean生成beanName。
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isTraceEnabled()) {
						logger.trace("Neither XML 'id' nor 'name' specified - " +
								"using generated bean name [" + beanName + "]");
					}
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}

当前层主要工作包括如下内容。
(1)提取元素中的id以及 name 属性。
(2)进一步解析其他所有属性并统一封装至 GenericBeanDefinition 类型的实例中。
(3) 如果检测到 bean 没有指定 beanName,那么使用默认规则为此 Bean 生成 beanName。
(4) 将获取到的信息封装到 BcanDefinitionHolder 的实例中。

3.AbstractBeanDefinition

至此我们便完成了对 XML 文档到 GenericBeanDefinition 的转换,也就是说到这里,XML中所有的配置都可以在 GenericBeanDefinition 的实例类中找到对应的配置

GenericBeanDefinition 只是子类实现,而大部分的通用属性都保存在了 AbstractBeanDefinition中,那么我们再次通过 AbstractBeanDefinition 的属性来回顾一下我们都解析了哪些对应的配置。

4.解析默认标签的自定义标签元素

场景:在默认的标签元素下,但是其中的’子元素标签’却使用了自定义的配置时,这句代码便会起作用了。
在这里插入图片描述

5.注册解析的 BeanDefinition

在这里插入图片描述

对于配置文件,解析也解析完了,装饰也装饰完了,对于得到的 beanDefinition 已经可以满足后续的使用要求了,唯一还剩下的工作就是注册了,也就是 processBeanDefinition 函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()代码的解析了。该方法做了两件事情:

	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		//  1、注册BeanDefinition(DefaultListableBeanFactory中存储)。格式:Map<beanName,BeanDefinition>。
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 2、注册别名(AliasRegistry中存储)。格式:Map<alias,beanName>。
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

6.通知监听器解析及注册完成

通过代码 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此工作,这里的实现只为扩展,当程序开发人员需要对注册 BeanDefinition 事件进行监听时可以通过注册监听器的方式并将处理逻辑写人监听器中,目前在 Spring 中并没有对此事件做任何逻辑处理。

三、alias标签的解析

在对 bean 进行定义时,除了使用 id 属性来指定名称之外,为了提供多个名称,可以使用alias 标签来指定

然而,在定义 bean 时就指定所有的别名并不是总是恰当的。有时我们期望能在当前位置为那些在别处定义的 bean 引入别名。在 XML 配置文件中,可用单独的< alias/>元素来完成bean 别名的定义。如配置文件中定义了一个 JavaBean:

<bean id = "testBean" class="com.test"/>

1、声明别名的两种方式

要给这个 JavaBean 增加别名,以方便不同对象来调用。我们就可以直接使用 bean 标签中的 name 属性:

<bean id="testBean" name= "testBean,testBean2" class="com.test"/>

同样,Spring 还有另外一种声明别名的方式:

<bean id="testBean" class="com. test"/>
<alias name="testBean" alias="testBean, testBean2" />

2、processAliasRegistration

protected void processAliasRegistration(Element ele) {
		// 获取beanName
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		// 获取alias
		String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
		boolean valid = true;
		if (!StringUtils.hasText(name)) {
			getReaderContext().error("Name must not be empty", ele);
			valid = false;
		}
		if (!StringUtils.hasText(alias)) {
			getReaderContext().error("Alias must not be empty", ele);
			valid = false;
		}
		if (valid) {
			try {
				// 注册alias
				getReaderContext().getRegistry().registerAlias(name, alias);
			}
			catch (Exception ex) {
				getReaderContext().error("Failed to register alias '" + alias +
						"' for bean with name '" + name + "'", ele, ex);
			}
			// 别名注册后通知相应监听器做处理
			getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
		}
	}

可以发现,跟之前讲过的 bean 中的 alias 解析大同小异,都是将别名与 beanName 组成一对注册至 registry 中。这里不再赘述。

四、import标签的解析

对于 Spring 配置文件的编写,我想,经历过庞大项目的人,都有那种恐惧的心理,太多的配置文件了。不过,分模块是大多数人能想到的方法,但是,怎么分模块,那就仁者见仁,智者见智了。使用 import 是个好办法,例如我们可以构造这样的 Spring 配置文件:

import resource= "customerContext.xml"/>
<import resource= "systemContext.xml" />

1、importBeanDefinitionResource

protected void importBeanDefinitionResource(Element ele) {
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
		// 如果不存在resource属性则不做任何处理
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}

		// 解析系统属性: e.g. "${user.dir}"
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

		Set<Resource> actualResources = new LinkedHashSet<>(4);

		// 判断location是绝对uri还是相对uri
		boolean absoluteLocation = false;
		try {
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
		catch (URISyntaxException ex) {
			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
		}

		// Absolute or relative?
		// 如果是绝对uri则根据地址直接加载文件
		if (absoluteLocation) {
			try {
				int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
				if (logger.isTraceEnabled()) {
					logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
				}
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
			}
		}
		else {
			// No URL -> considering resource location as relative to the current file.
			// 如果是相对uri则计算出绝对uri
			try {
				int importCount;
				//Resource 存在多个子实现类,如 VfsResource、 FileSystemResource 等,
				//而每个 resource 的 createRelative 方式实现都不一样,所以这里先使用子类的方法尝试解析
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				if (relativeResource.exists()) {
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
				else {
					//如果解析不成功,则使用默认的解析器 ResourcePatternResolver 进行解析
					String baseLocation = getReaderContext().getResource().getURL().toString();
					importCount = getReaderContext().getReader().loadBeanDefinitions(
							StringUtils.applyRelativePath(baseLocation, location), actualResources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
				}
			}
			catch (IOException ex) {
				getReaderContext().error("Failed to resolve current resource location", ele, ex);
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error(
						"Failed to import bean definitions from relative location [" + location + "]", ele, ex);
			}
		}
		// 解析后进行监听器激活处理
		Resource[] actResArray = actualResources.toArray(new Resource[0]);
		getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
	}

上面的代码不难,相信配合注释会很好理解,我们总结一下大致流程便于读者更好地梳理,在解析< import> 标签时,Spring 进行解析的步骤大致如下。
(1)获取 resource 属性所表示的路径。
(2)解析路径中的系统属性,格式如“${user.dir}”。
(3)判定 location 是绝对路径还是相对路径。
(4)如果是绝对路径则递归调用 bean 的解析过程,进行另一次的解析。
(5)如果是相对路径则计算出绝对路径并进行解析。
(6)通知监听器,解析完成。

五、嵌入式 beans 标签的解析

对于嵌人式的 beans 标签,相信大家使用过或者至少接触过,非常类似于 import 标签所提供的功能,使用如下:

<?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
	   		    https://www.springframework.org/schema/beans/spring-beans.xsd>

     <beans> 
    </beans>
    
</beans>

对于嵌入式 beans 标签来讲,并没有太多可讲,与单独的配置文件并没有太大的差别,无非是递归调用 beans 的解析过程,相信读者根据之前讲解过的内容已经有能力理解其中的奥秘了。

总结

待补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值