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 Obje

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

ctUtils.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 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);

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐