Spring(十):alias标签、import标签和beans标签的解析

本文详细介绍了Spring中alias和import标签的解析过程。alias标签用于为bean定义别名,其解析主要涉及校验、注册和处理循环引用问题;import标签则用于导入其他配置文件,解析时会根据资源路径加载bean定义。解析过程涉及到路径判断、资源加载和监听器通知。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

回顾

前面几篇,将Bean的标签解析整个流程弄懂了,但对于整个XML配置文件的解析是包括以下标签的

  • bean:用于创建IOC容器创建对象
  • alias:用于给Bean起别名
  • import:用于导入其他配置文件(有时候我们喜欢将Spring的配置文件分开单独处理,比如mybatis整合是一个文件、springmvc的整合又是另外一个文件,但Spring读取只会解析一个总文件,所以我们可以在总文件使用import标签导入其他配置文件)

回到我们刚开始的解析标签(解析标签的工作交由DefaultBeanDefinitionDocumentReader实现)
在这里插入图片描述
从这里开始去解析各类的标签,支持上面所示的4个标签

  • import
  • alias
  • bean
  • beans

下面来看一下alias标签、import标签如何解析的

alias标签解析

先去看下这个标签是如何进行使用的

<bean id="me" name="me0" class="xxx"/>
<alias name="me" alias="me1,me2"/>

在对bean提供名称的时候,Spring支持提供多个名称,所以有了alias标签去为bean指定别名,alias标签的alias的属性都指向同一个bean,起bean的id为me

下面就来看看容器是怎么解析alias标签的

对应的方法为processAliasRegistration

源码如下

	protected void processAliasRegistration(Element ele) {
        //获取alias标签里面的属性
        //获取name属性(name属性为对应的beanid)
		String name = ele.getAttribute(NAME_ATTRIBUTE);
        //获取alias属性(alias属性为对应的别名)
		String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
        //这个是一个校验操作结果标识
		boolean valid = true;
        //进行校验操作
        //判断name属性和alias属性是否为空
		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标签没有进行复杂的解析
                //这是因为alias标签就只有这两个属性
                //使用registrt进行注册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));
		}
	}

从代码上可以看到,对于alias标签没有bean标签的复杂,看到bean标签的解析流程,对于alias标签看起来就觉得很清秀

  • 获取name、alias属性

  • 进行校验操作

  • 校验通过就进行注册alias标签

    • 注册失败会抛错
  • alias标签注册成功就通知监听器

  • 细节点:校验alias标签失败(name属性为空或者alias属性为空),不会抛错。。

  • 注册工作交给AliasRegistrt接口(Bean的注册交给BeanDefinitionRegistry接口)

对于AliasRegistrt接口
在这里插入图片描述
可以看到这个接口是被BeanDefinitionRegistry接口继承了,而其默认的实现类依然是GenericApplicationContext
在这里插入图片描述
从代码上可以看到,GenericApplicationContext依然将注册的工作交给BeanFactory去做(DefaultListableBeanFactory)
在这里插入图片描述
但DefaultListableBeanFactory并没有重写这个方法,反而是交给了其父类SimpleAliasRegistry去实现了,所以归根到底是SimpleAliasRegistry负责Alias标签的注册

解析的源码如下

@Override
	public void registerAlias(String name, String alias) {
        //校验判断为空
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
        //进行底层的aliasMap进行加锁
        //(ConcurrentHashMap结构,而且这个容器在SimpleAliasRegistry中,默认容量为16)
        //同样Spring
		synchronized (this.aliasMap) {
            //如果alias属性与name属性相同
			if (alias.equals(name)) {
                //会删除掉原来的注册过的alias标签。。。
                //不太理解这种做法
                //alias属性与name属性相等,其实就是没有起别名嘛
                //而且Spring规定只有一个alias标签会起作用
                //但干嘛要删除掉之前起的别名呢????
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}
			else {
                // 如果alias与name属性不相同,证明alias标签是有效的
                //从容器中获取这个alias标签对应的name
				String registeredName = this.aliasMap.get(alias);
                //判断是否存在
				if (registeredName != null) {
                    //如果存在且name是相同的
                    //就代表这个alias标签重复了
                    //不需要进行重复注册,省略,所以如果有重复的alias并不会报错
					if (registeredName.equals(name)) {
						// An existing alias - no need to re-register
						return;
					}
                    //如果name不相同,那就代表出现歧义了
                    //判断是否可以进行覆盖
					if (!allowAliasOverriding()) {
                        //如果不能进行覆盖就抛错
						throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
                    //如果可以进行覆盖,输出日志
					if (logger.isDebugEnabled()) {
						logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'");
					}
				}
                //再次检查判断alias标签是否注册
                //这里判断蛮有意思的
				checkForAliasCircle(name, alias);
                //底层容器添加alias,key为alias,value为name
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}

可以看到,其实alias的注册跟beanDefinition差不多

  • 校验alias、name属性是否为空,不为空才能继续进行下一步
  • 给底层容器进行上锁(明明已经是ConcurrentHashMap,却还要进行Synchronic上锁处理,没必要吧)
  • 假如name和alias属性是相同的,代表alias标签是无效的,自己的别名起自己的名字?这是不符合常理的,所以,Spring的处理是认为这个alias标签是取消别名设置,即不需要别名(给自己的别名取自己的名字,不就是不需要别名吗?),就会将这个alias的之前的别名清空掉
  • 假如不相同,代表alias标签是有效的,再判断之前是或否已经有注册过这个alias
    • 如果注册过,判断是不是一样的name,一样的话直接Return
    • 不一样的话,判断支不支持覆盖,不支持覆盖会报错
  • 检查这个alias是否有循环引用问题
  • 往aliasMap添加alias与name,alias为key,name为value

检查是否有循环引用问题

举个栗子

其实就是存在键值对,alias0 -> name0 -> … ->alias0,通过alias0寻找name0,然后通过name0寻找另外一个alias,一直找下去,发现结果最终回到了alias0,这就是循环引用

在这里插入图片描述
源码如下所示,注意上一层传的参数,此时name其实是alias,alias其实为name

	public boolean hasAlias(String name, String alias) {
        //根据name去获取看有没有另外一个循环引用的name
		String registeredName = this.aliasMap.get(alias);
        //判断获取到的registeredName是否和alias是否一样,如果一样就代表出现循环依赖了,直接返回true给上层然后抛错
        //假如不一样,继续判断alias是否出现循环依赖
        //递归一直到alias对应的registeredName为空获取出现循环引用了
		return ObjectUtils.nullSafeEquals(registeredName, name) || (registeredName != null
				&& hasAlias(name, registeredName));
	}

可以看到,这里判断循环引用问题是一个递归过程

  • 判断当前的alias是否出现循环引用,方式是判断有没有已经注册的alias标签的alias属性与当前的name属性是一致的
  • 假如当前的name存在被引用,且其registeredName与alias是一致的,代表出现了循环引用问题
  • 假如当前的name存在被引用,但其registeredName与alias并不一致,并且不为空,那就递归找alias与registeredName是否出现循环引用
  • 假如如当前的name不存在被引用,代表没有出现循环引用问题

总结一下

对于alias标签的解析,有几个特殊需要注意的地方

  • 循环引用问题的解决,递归去判断与当前alias标签的alias属性是否出现了循环引用
  • 对于alias属性与name属性一致的问题,Spring会默认为取消alias别名

import标签解析

import标签可以让我们将庞大的ApplicationContext.xml细分成每个小的XML,比如Mybatis、SprigMVC进行拆分
在这里插入图片描述
下面就来看看是import标签是怎样解析的
在这里插入图片描述
源码如下

	protected void importBeanDefinitionResource(Element ele) {
        //获取import标签的resource属性
		String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
        //判空
		if (!StringUtils.hasText(location)) {
			getReaderContext().error("Resource location must not be empty", ele);
			return;
		}

		// Resolve system properties: e.g. "${user.dir}"
        //处理resource属性,因为可以采用配置环境方式的
		location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

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

		// Discover whether the location is an absolute or relative URI
		boolean absoluteLocation = false;
		try {
            //判断是不是URL,如果不是URL就转换成URL并且判断是不是绝对路径
			absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
		}
       //如果不能转化成URL代表可能是相对路径
        //所以捕捉异常不会直接抛错,absoluteLocation仍然为false,代表为相对路径
		catch (URISyntaxException ex) {

			// cannot convert to an URI, considering the location relative
			// unless it is the well-known Spring prefix "classpath*:"
		}

		// Absolute or relative?
        //如果是相对路径
		if (absoluteLocation) {
			try {
                //执行loadBeanDefinitions解析import的resource指向的XML文件
                //前面已经看过了,就不赘述了
				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.
			try {
                //转换成相对路径
				int importCount;
				Resource relativeResource = getReaderContext().getResource().createRelative(location);
				//判断相对路径是否存在
                if (relativeResource.exists()) {
                    //如果存在进行解析XML文件
					importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
					actualResources.add(relativeResource);
				}
                //假如相对路径不存在
                //代表可能改了相对地址
				else {
                    //尝试将路径改为相对地址+相对路径再去解析XML文件
					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标签其实本质上就是解析了resource属性,然后去调用loadBeanDefinitions去解析XML文件,所以关键就在于对路径的解析和判断而已

  • 首先判断是不是URL
    • 如果不是URL,转化成URI,并且判断是不是绝对路径
    • 如果是URL,那就代表是绝对路径了,解析XML文件
  • 如果不是URL,并且转化成URI失败,那就代表可能是相对路径(所以捕捉转化失败的异常不会进行处理)
  • 尝试转化为相对路径
    • 如果转化成功,那就根据路径去解析XML文件
    • 如果转化失败,那就尝试使用拼接的方式计算出绝对路径,拼接当前总配置文件的路径和resource属性形成路径,然后去解析XML文件
  • 最后通知监听器,解析完成

Beans标签解析

其实这个本质上就是循环递归调用beans的解析过程

在这里插入图片描述
从之前解析Bean标签的时候可以看出,其实本质上就是循环递归去调用parseDefault方法,对于beans下的子标签进行遍历,递归调用parseDefaultElement方法,又判断这个子标签是4个标签的哪个(beans,bean,import,alias),对应又执行哪种标签的解析方法

至此,Spring对配置文件的默认4个标签的解析就结束了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值