提到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的名字的由来。
-
只指定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,只要他不叫张三就好。 -
只指定bean的id。
在声明bean的时候,可以不指定bean的名字而是指定bean的id,这时它的id就是他的名字。如上图service3 -
同时指定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。