搞清楚beanName、id、name和alias

基于前面介绍的用spring改造后的示例应用,我们将做一些实验来了解bean定义的beanName、id、name属性以及别名映射的用法,并做相关总结。

参考练习examples/spring-02-id-name-alias

打印beanName

我们在BookStoreAppTest.saveBook单元测试方法中输出加载到的所有bean定义的名称,也就是beanName,bean定义在注册时就是用beanName作为唯一标识来进行bean定义的注册的。

package com.xiaoma.spring.example.bookstore;

import ...

@Slf4j
class BookStoreAppTest {

   @Test
   void saveBook() {
      ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
      ...

      AbstractApplicationContext aac = (AbstractApplicationContext) context;
      String[] beanDefinitionNames = aac.getBeanDefinitionNames();
      log.info("-------------------容器中包含的bean定义的名称列表------------------");
      for (String name : beanDefinitionNames) {
         log.info("{}", name);
      }
   }
}

看到输出的结果:

21:47:20.453 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - -------------------容器中包含的bean定义的名称列表------------------
21:47:20.453 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - funnyBook
21:47:20.453 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - boringBook
21:47:20.454 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - dabao
21:47:20.454 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - erbao
21:47:20.454 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - bookStoreApp
21:47:20.454 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - bookDao
21:47:20.455 [Test worker] INFO  c.x.s.e.b.BookStoreAppTest - bookService
省略id的情况

在前面我们声明的bean定义中,只指定了id属性,则spring容器在注册bean定义时就会取id作为beanName。在同一个xml配置文件中id是不能重复的,这是xml本身定义中的约束。当然,id不是必须的,在声明一个bean时不指定id,这里区分两种情况:

  • 内部bean

    内部bean只有当前类的实例自己会引用,不会被外部环境引用到,通常直接用内部bean作为构造器参数或者属性值进行注入。比如:

    <bean id="jack" class="com.xiaoma.spring.example.reading.Child">
       <property name="name" value="jack" />
       <property name="book">
          <bean class="com.xiaoma.spring.example.reading.Book" />
       </property>
    </bean>
    

    这里的Child实例化后注入的book依赖就是一个内部bean,它只与当前的实例绑定。它是没有名称的,因为外部没有对它的引用,也不需要对其进行bean定义的注册。

  • 外部bean

    相对于内部bean来说,如果不指定任何名称,容器会为外部bean自行分配。如果容器中只有该类型的bean的唯一实例,则我们无需显式为其指定名称;否则在按照名称进行注入时,不指定idname,会很不方便。比如:

    <!-- com.xiaoma.spring.example.reading.Book#0 -->
    <bean class="com.xiaoma.spring.example.reading.Book" />
    <!-- com.xiaoma.spring.example.reading.Book#1 -->
    <bean class="com.xiaoma.spring.example.reading.Book" />
    

    这里虽然我们定义了两个一样的bean(class一样),它们也是作为两个bean被注册的,注册用的beanName是自动生成的,虽然这种情况在我们后面介绍通过组件注解来声明bean时不会出现,但是在使用传统xml方式声明bean时更推荐显式的指定idname属性,尤其是涉及多个同类型的bean的定义。

name属性与别名映射

再来看name属性,它可以用来为bean起多个名字,它比起id属性的好处是可以设置多个名称,通过逗号分隔(中间可以有空格)来设置。name属性可以单独指定,也可以和id一起指定,但是要注意,在同一个配置文件中,用idname指定的任何一个名称都必须唯一(在单个bean定义中有重复会去重),不能和其他bean定义中的名称冲突,否则会报错。看下面的bean定义:

<!-- 命名规则没有严格限制,名称中也可以出现空格、点号、中划线等 -->
<bean id="myBook" name="bookForChildren, myBook, childrenBook, bookForChildren, children.book"
		  class="com.xiaoma.spring.example.reading.Book" />

在当前bean定义中有名称重复没关系,会去重,实际为:

<bean id="myBook" name="bookForChildren, childrenBook, children.book" ... />

这里myBook作为注册bean定义的beanName,其他的名称作为其别名,通过调试源码,可以看到别名的注册信息:

在这里插入图片描述

通过测试输出,会发现这4个名称是互为别名的关系

// AbstractApplicationContext aac
log.info("myBook alias: {}", Arrays.toString(aac.getAliases("myBook")));
log.info("bookForChildren alias: {}", Arrays.toString(aac.getAliases("bookForChildren")));
log.info("childrenBook alias: {}", Arrays.toString(aac.getAliases("childrenBook")));
log.info("children.book alias: {}", Arrays.toString(aac.getAliases("children.book")));

这种别名的映射等同于下面的显式指定别名映射的配置形式:

<alias name="myBook" alias="bookForChildren" />
<alias name="bookForChildren" alias="childrenBook" />
<alias name="childrenBook" alias="children.book" />

<bean id="myBook" class="com.xiaoma.spring.example.reading.Book" />

存储的映射关系可以看到:

在这里插入图片描述

会形成一个别名链,和前面多对一的映射,在获取每个名称的alias时,得到的结果都是一样的,都是互为别名的关系。

但是在注册别名链时需要注意不能存在循环注册的情况,比如:

<alias name="myBook" alias="bookForChildren" />
<alias name="bookForChildren" alias="childrenBook" />
<alias name="childrenBook" alias="children.book" />
<!-- 和第一个形成了循环注册 -->
<alias name="children.book" alias="myBook" />

启动容器提示的错误:

Configuration problem: Failed to register alias 'myBook' for bean with name 'children.book'
Offending resource: class path resource [application-context.xml]; nested exception is java.lang.IllegalStateException: Cannot register alias 'myBook' for name 'children.book': Circular reference - 'children.book' is a direct or indirect alias for 'myBook' already
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to register alias 'myBook' for bean with name 'children.book'

以上对别名的映射关系也支持仅定义name属性的情况,beanName会使用name中指定的第一个值作为注册bean定义的唯一标识,其他值会作为其别名。可以由读者自行实验。

在实际获取bean时,可以使用任何一个名称,下面的断言是ok的,容器会根据建立的别名映射找到注册bean的beanName来获取bean:

assertSame(context.getBean("bookForChildren"), context.getBean("myBook"));
文件覆盖的情况

在同一个配置文件中进行如下配置:

<alias name="myBook" alias="bookForChildren" />
<alias name="bookForChildren" alias="childrenBook" />
<alias name="childrenBook" alias="children.book" />

<bean id="myBook" class="com.xiaoma.spring.example.reading.Book" />

<bean name="childrenBook" class="com.xiaoma.spring.example.reading.Book" />

下面的断言是成立的:

assertSame(context.getBean("children.book"), context.getBean("myBook"));

说明在使用一个名称获取bean时,会判断别名链,找到最原始的beanName,即idmyBook的bean。断开中间的别名映射:

<!--	<alias name="bookForChildren" alias="childrenBook" />-->

现在获取的才是两个不同的bean。

对于跨多个文件的配置,Spring容器加载多个xml文件或者使用<import />包含子配置文件时,如果定义的bean的标识字段有冲突,后面加载的会覆盖前面的。比如,这里有两个xml文件:

<!-- 位于application-context.xml文件中 -->
<bean id="myBook" name="bookForChildren, childrenBook, children.book" class="com.xiaoma.spring.example.reading.Book">
    <property name="name" value="海贼王" />
    <property name="type" value="漫画" />
</bean>

<!-- 位于other-context.xml文件中 -->
<bean name="myBook" class="com.xiaoma.spring.example.reading.Book">
    <property name="name" value="火影" />
    <property name="type" value="漫画" />
</bean>

加载顺序:

ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml", "other-context.xml");

或者在application-context.xml中导入other-context.xml

<beans ...>

	<bean id="myBook" name="bookForChildren, childrenBook, children.book" class="com.xiaoma.spring.example.reading.Book">
		<property name="name" value="海贼王" />
		<property name="type" value="漫画" />
	</bean>

	<import resource="other-context.xml" />
</beans>

会发现根据myBook获取的bean是被覆盖了注册定义的火影漫画。

现在调整下other-context.xml种的定义:

<bean id="yourBook" name="childrenBook" class="...">
    ...
</bean>

从输出的日志可以看到现在各个名称的别名:

myBook alias: 			[bookForChildren, children.book]
bookForChildren alias: 	[myBook, children.book]
childrenBook alias: 	[yourBook]
children.book alias: 	[myBook, bookForChildren]

很显然现在拆成了两个别名组合了,也就是存在别名映射的覆盖。自然下面的断言不会成立:

assertSame(context.getBean("childrenBook"), context.getBean("myBook"));
最佳实践

推荐的做法是,如果某类型的bean只有一个,指定id为类名首字母小写,如果存在多个指定时后缀加以区分,另外bean的命名和变量不一样,没那么多限制,比如bookDao-mockbookDao.mock都是合法的

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java小卷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值