博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
谈谈spring中bean的名字
阅读量:6942 次
发布时间:2019-06-27

本文共 7064 字,大约阅读时间需要 23 分钟。

hot3.png

提到bean的名字,就要从声明bean的地方说起。在应用spring时,有两个地方可以声明一个bean,一个是在spring的配置文件中,一个是在代码中通过Component等标注声明.

代码中可以通过标注的方式来表示这个类是属于spring管理的类,这类标注有Component、Repository、Service以及Controller。它们默认没有直接指定bean的名字,所以bean的名字是spring默认生成的,这些bean的名字生成规则就是bean的类型首字母小写。这个会则会引起一个问题,若不同的包下有两个名字相同的类,而这两个类都声明成spring的bean,这时候就会产成冲突。因为bean的名字就是bean的唯一标示,是不允许重复的。当然我们可以在标注中指定bean的名字就解决了这个问题,比如Controller("bean9527"),这样当前Controller的名字就是"bean9527"了

前面在讲依赖注入中涉及到获取依赖bean的时候用到的方法都是getBean(String beanName),可见知道bean的名字就可以找到相应的bean。那么bean只能有一个名字吗,当然不是,我们可以给bean取多个名字,也就是别名alias。另外,我们在配置文件中声明bean的时候,不仅可以指定bean的名字还可以指定bean的id,那么bean的名字和id之间是什么关系呢?还有一种情况就是即不指定bean的名字,也不指定bean的id,只是单单指定bean的类型,这时候bean的名字又是怎样的呢?下面通过一段配置来看看spring是怎么管理bean的名字的

假设在spring的配置文件中声明如上所示的bean,那么spring会怎么生成和保存这些bean的名字呢?透过现象看本质,先来看看现象是怎样的

输入图片说明

上图的截图来自beanFactory中的singletonObjects,这个singletonObjects在AbstractBeanFactory的基类DefaultSingletonBeanRegistry中。它是一个map结构,key就是bean的名字,value就是bean本身,它保存了当前beanFactory中注册的所有的单例bean。利用图中的结果我们来一一分析一下bean的名字的由来。

  1. 只指定bean的类型

    若只指定了bean的类型,spring为bean生成名字是bean类型的全限定名加编号组成。可以看到,我们在配置文件中声明两个com.test.service.Service1类型的bean,而他们对应的名字就是com.test.service.Service1#0和com.test.service.Service1#1。居然有两个类型相同的单例bean,可见spring中单例的概念和传统的单例并不相同。spring中单例不是相对类型而言,而是相对于我们定义的bean。也就是说如果我们定义了一个bean,他叫“张三”,那么在spring中就只有一个张三的实例,不论何时你看到的都是他。但是我们还可以定义一个和张三一模一样的bean,只要他不叫张三就好。

  2. 只指定bean的id。

    在声明bean的时候,可以不指定bean的名字而是指定bean的id,这时它的id就是他的名字。如上图service3

  3. 同时指定bean的id,名字和类型。

    由上面的service4和service6的例子可以看出,只要是指定了bean的id,存储在singletonObjects中的名字就是这个id。那么通过配置中的name以及类的全名就找不到了吗?bean的别名在哪里,怎么利用别名查找bean?接下来通过源码进行分析,看看事情的本质。

**一、BeanDefinition解析时bean名字的识别与生成 **

要解释上面的现象,首先要从BeanDefinition的解析说起,下面就是处理配置文件中bean标签的方法

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {         //获取
元素中的id属性值 String id = ele.getAttribute(ID_ATTRIBUTE); //获取
元素中的name属性值 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); 获取
元素中的alias属性值 List
aliases = new ArrayList
(); //将
元素中的所有name属性值存放到别名中 if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; //如果
元素中没有配置id属性时,将别名中的第一个值赋值给beanName if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } //检查
元素所配置的id或者name的唯一性,containingBean标识
//元素中是否包含子
元素 if (containingBean == null) { //检查
元素所配置的id、name或者别名是否重复 checkNameUniqueness(beanName, aliases, ele); } //详细对
元素中配置的Bean定义进行解析的地方 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { if (containingBean != null) { //如果
元素中没有配置id、别名或者name,且没有包含子//
元素,为解析的Bean生成一个唯一beanName并注册 beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { //如果
元素中没有配置id、别名或者name,且包含了子//
元素,为解析的Bean使用别名向IoC容器注册 beanName = this.readerContext.generateBeanName(beanDefinition); //为解析的Bean使用别名注册时,为了向后兼容 //Spring1.2/2.0,给别名添加类名后缀 String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("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); } //当解析出错时,返回null return null; }

上面的方法定义在BeanDefinitionParserDelegate中,这个方法诠释了bean的名字以及别名的确定过程。首先,配置文件中name中是可以指定bean的多个名字的,每个名字中间用逗号、分号或者空格隔开。若指定了id,则bean的名字就用它了,完全符合刚才的service4及service6。若没有指定bean的id,则取bean的第一名字作为它真实的名字。若即没有指定id也没有指定那么,那么就根据bean的类型生成一个,就像刚才的service1。这里有个特殊情况,就是第一个声明的bean会使用类型的全限定名作为bean的别名,刚刚的com.test.service.Service1#0会使用com.test.service.Service1作为它的别名。

bean的别名不仅可以自动生成,更是可以通过alias标签显示指定,对于alias标签定义的别名的处理在DefaultBeanDefinitionDocumentReader的下面方法中

** 二、BeanDefinition注册时对bean名字的处理 **

回顾一下BeanDefinition在BeanDefinitionReaderUtils中注册时是怎么处理bean的名字的

public static void registerBeanDefinition(          BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)          throws BeanDefinitionStoreException {        // Register bean definition under primary name.      String beanName = definitionHolder.getBeanName();      registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());        // Register aliases for bean name, if any.      String[] aliases = definitionHolder.getAliases();      if (aliases != null) {          for (String aliase : aliases) {              registry.registerAlias(beanName, aliase);          }      }  }

可以看到BeanDefinition注册时,记录了bean的名字和BeanDefinition的映射关系。bean的别名信息并没有保存在BeanDefinition中,而是单独进行了注册。看一下别名的注册过程,它定义在SimpleAliasRegistry中

public void registerAlias(String name, String alias) {      Assert.hasText(name, "'name' must not be empty");      Assert.hasText(alias, "'alias' must not be empty");      if (alias.equals(name)) {          this.aliasMap.remove(alias);      }      else {          if (!allowAliasOverriding()) {              String registeredName = this.aliasMap.get(alias);              if (registeredName != null && !registeredName.equals(name)) {                  throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +                          name + "': It is already registered for name '" + registeredName + "'.");              }          }          checkForAliasCircle(name, alias);          this.aliasMap.put(alias, name);      }  }

别名的注册其实就是保存了别名到真实名字(可能也是另个名字的别名)的映射关系,这个SimpleAliasRegistry是DefaultSingletonBeanRegistry的基类,他们都是AbstractBeanFactory的基类。由此可见,我们常用的BeanFactory不仅是BeanDefinition的注册中心,还是单例对象的注册中心以及bean的别名注册中心。

** 三、利用名字查找bean的过程 **

在AbstractBeanFactory中的doGetBean方法的第一行就是关于bean名字的处理 final String beanName = transformedBeanName(name) 。这里就实现了bean的名字的转换,转换过程在它调用的canonicalName方法中

public String canonicalName(String name) {      String canonicalName = name;      // Handle aliasing...      String resolvedName;      do {          resolvedName = this.aliasMap.get(canonicalName);          if (resolvedName != null) {              canonicalName = resolvedName;          }      }      while (resolvedName != null);      return canonicalName;  }

在想要获取一个bean的时候,做的第一件事情就是找到bean的真实名字,因为找到bean的真实名字才能找到对应的BeanDefinition或其单例实例。找到bean真实名字的方法就是根据层层的别名关系,直到找出这样一个名字,这个名字在aliasMap中作为别名已经找不到对应的真实bean名字,也就是说这个名字已经不是别名就是bean的名字。所以,不论根据bean的名字,还是任意一个别名都能从容器中取得到相应的bean。

转载于:https://my.oschina.net/thinwonton/blog/1137789

你可能感兴趣的文章
完善(用户调研反馈+自评+典型用户与场景)
查看>>
elasticsearch接口开发(新)
查看>>
How to configure your MyInbox webpart automatically ?
查看>>
Linux : centOS 与 Ubuntu 安装 Nginx
查看>>
Django&Admin站点&调整站点信息
查看>>
POJ2125 Destroying The Graph
查看>>
详细的App推广前的准备工作
查看>>
15年1月的每天小程序
查看>>
多选插件multiselect.js
查看>>
Mysql基本用法-存储引擎-04
查看>>
使用GregorianCalendar模拟实现查看当前月的日历
查看>>
linux下的视频音频播放器终极解决方案
查看>>
让程序跨进网络时代——使用C语言获取百度源代码
查看>>
egret 精简游戏项目
查看>>
第 1 章 虚拟化 - 009 - KVM 网络虚拟化基础
查看>>
OpenJDK 源代码阅读之 BitSet
查看>>
观察者模式
查看>>
写给产品经理的技术书:客户端、服务端和交互相关技术
查看>>
Here's to the crazy ones.
查看>>
react router browserhistory 关于 Nginx配置
查看>>