谈谈spring中bean的名字

谈谈spring中bean的名字

     提到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的名字的
	<bean class="com.test.service.Service1"/>
	<bean class="com.test.service.Service1"/>
	<bean id="service3" class="com.test.service.Service3" init-method="initMethod">
		<property name="service4" ref="service4"/>
	</bean>
	<bean name="service4" id="service42" class="com.test.service.Service4" >
		<property name="service3" ref="service3"/>
		<property name="service6" ref="service64"/>
	</bean>
	<bean id="service6" name="service61"  class="com.test.service.Service6" autowire="byName"/>
	<alias name="service6" alias="service62"/>
	<alias name="service61" alias="service63"/>
	<alias name="service63" alias="service64"/>
    假设在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) {
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		List<String> aliases = new ArrayList<String>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		String beanName = id;
		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");
			}
		}

		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}

		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						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);
		}

		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的下面方法中
	protected void processAliasRegistration(Element ele) {
		String name = ele.getAttribute(NAME_ATTRIBUTE);
		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 {
				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));
		}
	}

二,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。


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值