谈谈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的下面方法中
二,BeanDefinition注册时对bean名字的处理
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。