IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> [Spring源码]——默认标签的解析 -> 正文阅读

[Java知识库][Spring源码]——默认标签的解析

笔记整理自《Spring源码深度解析》(第2版),同时也参考了一些网上资源,具体参考链接在文末

整体分析

标签分类

  • 默认标签
    • alias 标签
    • bean 标签
    • beans 标签
    • import 标签
  • 自定义标签

bean标签的解析及注册流程

在默认标签中,对bean标签的解析最为复杂也最为重要,我们从此标签分析其解析流程,即可理解其他标签的解析

bean标签的解析及注册时序图.drawio

  1. 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class,name,id,alias之类的属性。
  2. 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
  3. 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了Bean-
    DefinitionReaderUtils的registerBeanDefinition方法。
  4. 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。

源码分析

parseBeanDefinitions 分析

DefaultBeanDefinitionDocumentReader.java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
   // 是否是默认的命名空间
   if (delegate.isDefaultNamespace(root)) {
      // 子节点列表
      NodeList nl = root.getChildNodes();
      for (int i = 0; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if (node instanceof Element) {
            Element ele = (Element) node;
            // 是否是默认的命名空间
            if (delegate.isDefaultNamespace(ele)) {
               // 处理标签的方法
               parseDefaultElement(ele, delegate);
            }
            else {
               // 处理自定义标签
               delegate.parseCustomElement(ele);
            }
         }
      }
   }
   else {
      // 处理自定义标签
      delegate.parseCustomElement(root);
   }
}

parseDefaultElement分析

DefaultBeanDefinitionDocumentReader.java
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
   // 解析 import 标签
   if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
      importBeanDefinitionResource(ele);
   }
   // 解析 alias 标签
   else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
      processAliasRegistration(ele);//(4)
   }
   // 解析 bean 标签
   else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
      processBeanDefinition(ele, delegate);//(2)
   }
   // 解析 beans 标签
   // 嵌套的 beans
   else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
      // recurse
      doRegisterBeanDefinitions(ele);
   }
}

对于不同类型的默认标签有不同的处理方法,接下来我们逐个分析

bean标签

(1)processBeanDefinition 方法解析

DefaultBeanDefinitionDocumentReader.java
// 删除了异常处理
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
   // 创建 bean definition
   BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); //(2)
   if (bdHolder != null) {
      // bean definition 装饰
      bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
      // Register the final decorated instance.
      // 注册beanDefinition
      BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); //(3)
      // Send registration event.
      // component注册事件触发
      getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));//(4)
   }
}

decorateBeanDefinitionIfRequired的作用是当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时,就需要进行装饰处理。

Spring 在这里将 bean 标签的处理完全交给了 BeanDefinitionParserDelegate 对象进行。

流程分析:

  1. 交由 BeanDefinitionParserDelegate 对象进行 bean 标签的处理得到 BeanDefinitionHolder 对象
  2. 将得到的 BeanDefinitionHolder 对象进行 Bean 定义注册
  3. 发布 Bean 注册事件

fireComponentRegistered为通知监听器解析以及注册完成,这里的实现只为扩展,如果需要对注册BeanDefintion事件进行监听注册时可以通过注册监听器的方式将处理器逻辑写入监听器中。

(2)parseBeanDefinitionElement分析

BeanDefinitionParserDelegate.java

此方法较为复杂,我们分为几部分分析:

第一部分代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    // 第一部分
    // 获取 id
    String id = ele.getAttribute(ID_ATTRIBUTE);
    // 获取 name
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

    // 别名列表
    List<String> aliases = new ArrayList<>();
    // 是否有 name 属性
    if (StringUtils.hasLength(nameAttr)) {
        // 获取名称列表, 根据 `,; `进行分割
        String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        // 添加所有
        aliases.addAll(Arrays.asList(nameArr));
    }
    ...
}

第一部分的处理很简单获取 bean 标签中的 idname 属性,对 name 属性做分隔符切分后将切分结果作为别名

第二部分的代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    ...
        String beanName = id;
    //如果id为空,且别名列表不为空
    if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
        // 别名的第一个设置为beanName
        beanName = aliases.remove(0);
        if (logger.isTraceEnabled()) {
            logger.trace("No XML 'id' specified - using '" + beanName +
                         "' as bean name and " + aliases + " as aliases");
        }
    }

    // bean definition 为空
    if (containingBean == null) {
        // 判断 beanName 是否被使用, bean 别名是否被使用
        checkNameUniqueness(beanName, aliases, ele);
    }
    ...
}
  1. id 存在的情况下都是用 id 作为 Bean Name

  2. id 不存在的情况下使用 name 被分隔符切分后的第一个元素作为 Bean Name

第三部分的代码
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    ...
       //调用parseBeanDefinitionElement对标签其他属性进行解析
        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);

                    String beanClassName = beanDefinition.getBeanClassName();
                    if (beanClassName != null &&
                        beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                        !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                        aliases.add(beanClassName);
                    }
                }
            }
            catch (Exception ex) {
               ...
            }
        }
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

注意其中

  AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

它会进一步解析其他所有属性,并统一封装至GenericBeanfinition类型的实例中。GenericBeanfinition是AbstractBeanDefinition的子类

image-20220203211447916

BeanDefinition 是用于属性承载的接口,在 Spring 中存在三种实现: RootBeanDefinition 、childBeanDefinition 以及 GenericBeanDefinition

小结
  1. 提取元素中的id以及name属性。

  2. 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。

3、如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName

4、将获取到的信息封装到BeanDefinitionHolder的实例中。

拿到BeanDefinitionHolder实例后,接下来又回到了(1)方法中,我们可以看到接下来需要进行注册了

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); //(3)

(3)注册解析的 Bean Definition

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

   // Register bean definition under primary name.
   // 获取 beanName
   String beanName = definitionHolder.getBeanName();
   // 注册bean definition
   registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());//

   // Register aliases for bean name, if any.
   // 别名列表
   String[] aliases = definitionHolder.getAliases();
   // 注册别名列表
   if (aliases != null) {
      for (String alias : aliases) {
         registry.registerAlias(beanName, alias);
      }
   }
}

整个过程分为两部分;

  1. 第一步:通过beanName的注册
  2. 第二步:通过alias的注册
  • 首先来分析第一步

    DefaultListableBeanFactory.java
    //成员变量,用来存储beanDefinition
    private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
    			new ConcurrentHashMap<>(8);
    //用来记录已经注册了的beanDefinitionName,主要用于多线程下的判断
    /** List of bean definition names, in registration order. */
    	private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException {
    
    		Assert.hasText(beanName, "Bean name must not be empty");
    		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
    		if (beanDefinition instanceof AbstractBeanDefinition) {
    			try {
                    /*
                    *注册前的最后一次校验,这里的校验不同于之前的XML文件校验
                    */
    				((AbstractBeanDefinition) beanDefinition).validate();
    			}
    			catch (BeanDefinitionValidationException ex) {
    			 ...
    			}
    		}
    
        	//beanName存在BeanDefinitio的情况
        	//从map中根据beanName获取beanDefinition
    		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    		if (existingDefinition != null) {
                //bean name是否允许重复注册
    			if (!isAllowBeanDefinitionOverriding()) {
    				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
    			}
                //role值比较
    			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
    				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
    			...
    			}
                //map中存储的beanDefintion是否和参数相同
    			else if (!beanDefinition.equals(existingDefinition)) {
    				 if (logger.isDebugEnabled()) {
             logger.debug("Overriding bean definition for bean '" + beanName +
                   "' with a different definition: replacing [" + existingDefinition +
                   "] with [" + beanDefinition + "]");
          }
    			}
    			else {
    				...
    			}
                //如果允许覆盖Bean Definition,通过beanName进行注册
    			this.beanDefinitionMap.put(beanName, beanDefinition);
    		}
        	//容器中不存在Bean Name对应的Bean Definitio的处理
    		else {
                //检查bean是否已经开始创建
    			if (hasBeanCreationStarted()) {
    				// Cannot modify startup-time collection elements anymore (for stable iteration)
    				synchronized (this.beanDefinitionMap) {
                        //通过beanName进行注册
    					this.beanDefinitionMap.put(beanName, beanDefinition);
                        //bean definitio的名称列表
    					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
    					//加入当前的beanName
                 updatedDefinitions.addAll(this.beanDefinitionNames);
    					updatedDefinitions.add(beanName);
                        //对象替换
    					this.beanDefinitionNames = updatedDefinitions;
                        //移除当前beanName
    					removeManualSingletonName(beanName);
    				}
    			}
    			else {
    				// Still in startup registration phase
                    //通过beanName进行注册
    				this.beanDefinitionMap.put(beanName, beanDefinition);
    				this.beanDefinitionNames.add(beanName);
    				removeManualSingletonName(beanName);
    			}
    			this.frozenBeanDefinitionNames = null;
    		}
    
        	//Bean Definition的刷新处理
        	//两个条件:1. 通过beanName搜索BeanDefinition成功 2. 单例对象容器中包含当前BeanName的实例对象
    		if (existingDefinition != null || containsSingleton(beanName)) {
                //重置所有beanName对应对应的缓存
    			resetBeanDefinition(beanName);
    		}
    		else if (isConfigurationFrozen()) {
    			clearByTypeCache();
    		}
    	}
    

    很多人认为注册beanDefinition就是将beanDefinition直接放入map中,使用beanName作为key。不过除此之外,Spring还做了其他事情。具体流程如下:

    • 对AbstractBeanDefinition的校验。之前的校验时针对于XML格式的校验,而此时的校验时针是对于AbstractBean-Definition的methodOverrides属性
      • 对beanName已经注册的情况的处理。如果设置了不允许bean的覆盖,则需要抛出异常,否则直接覆盖
      • 多线程中,如果有其他线程正在创建当前beanName,则尝试获取锁,获取成功进行注册,然后加入map缓存,加入beanName的list中。
      • 无以上情况直接注册,将其加入map缓存。
    • 清除解析之前留下的对应beanName的缓存。
  • 分析第二步

    SimpleAliasRegistry.java
    // 删除日志和验证相关代码
    public void registerAlias(String name, String alias) {
        synchronized(this.aliasMap) {
            // 别名和真名是否相同
            if(alias.equals(name)) {
                // 移除
                this.aliasMap.remove(alias);
            } else {
                // 通过别名获取真名
                String registeredName = this.aliasMap.get(alias);
                // 真名不为空
                if(registeredName != null) {
                    // 真名等于参数的真名
                    if(registeredName.equals(name)) {
                        // An existing alias - no need to re-register
                        return;
                    }
                    // 是否覆盖别名
                    if(!allowAliasOverriding()) {
                        throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
                    }
                }
                // 别名是否循环使用
                checkForAliasCircle(name, alias);
                // 设置 别名对应真名
                this.aliasMap.put(alias, name);
            }
        }
    }
    

    整个方法以这样一个判断作为分支: 判断别名是否和真名相等,相等时候的处理很简单,从别名容器中剔除这个别名的键值信息。

    接下来就是对于别名和真名不相等的情况处理了。

    第一步:从别名容器中用别名去获取容器中可能存在的真名,

    第二步:当容器中可能存在的真名存在的情况下会和参数真名进行比较是否相同 , 相同的话就直接返回了, 不做其他处理。如果参数不相同,且不允许覆盖的话,则报错

    第三步:当容器中可能存在的真名不存在的情况下 Spring 会进行别名是否循环使用,当检测通过时加入到别名容器中。

alias标签解析

在对 bea 进行定义时,除了使用 id 属性来指定名称之外,为了提供多个名称,可以使用 alias 标签来指定 而所有的这些名称都指向同一个 bean ,在某些情况下提供别名非常有用,比 如为了让应用的每 个组件能更容易地对公共组件进行引用

我们回到parseDefaultElement方法中的processAliasRegistration中继续分析如何进行alias标签解析的

(4)processAliasRegistration解析

DefaultBeanDefinitionDocumentReader.java
protected void processAliasRegistration(Element ele) {
       //获取beanName
		String name = ele.getAttribute(NAME_ATTRIBUTE);
      //获取alisa
		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().fireAliasRegistered(name, alias, extractSource(ele));
		}
	}

import标签解析

applicationContext.xml文件中使用import的方式导入有模块配置文件,以后若有新模块的加入,那就可以简单修改这个文件了。这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理

我们回到parseDefaultElement方法中的importBeanDefinitionResource中继续分析如何进行import标签解析的

(5)importBeanDefinitionResource解析

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

    //解析系统属性,格式如:"${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) {
      
    }

    //如果是绝对URI则自己根据地址加载对应的配置文件
    if (absoluteLocation) {
        try {
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
   			...
        }
        catch (BeanDefinitionStoreException ex) {
         ..
        }
    }
    else {
        //如果是相对地址则根据相对地址计算出绝对地址
        try {
            int importCount;
            //Resource存在多个子实现类,而每个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);
            }
          ...
        }
        catch (IOException ex) {
         ...
        }
    }
    //解析后进行监听器激活处理
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

流程分析:

  1. 获取resource属性所表示的路。
  2. 解析路径中的系统属性,格式如"S{user.dir}"
  3. 判定location是绝对路径还是相对路径。
  4. 如果是绝对路径则递归调用bean的解析过程,进行另一次的解析。
  5. 如果是相对路径则计算出绝对路径并进行解析。
  6. 通知监听器,解析完成。

参考链接:

第三章 IoC 资源读取及注册

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-05 21:35:51  更:2022-02-05 21:36:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:40:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码