Spring in Action 4读书笔记之解决自动装配bean的二义性问题

Spring in Action(Spring实战)的第三章第三节(3.3 Addressing ambiguity in autowiring)讲述了如何解决自动装配bean的二义性问题。

Spring的自动注入机制极大地减少装配bean的工作量,但在默认情况下,这个机制仅在只有一个bean满足条件时才工作。当有多个bean满足条件时,这种二义性使Spring无法确定使用哪一个bean,也就无法完成自动装配,并且会抛出NoUniqueBeanDefinitionException异常。

比如下面的代码就存在二义性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Autowired
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

@Component
public class Cake implements Dessert { ... }

@Component
public class Cookies implements Dessert { ... }

@Component
public class IceCream implements Dessert { ... }


因为Cake,Cookies,IceCream都实现了Dessert接口,Dessert声明的bean存在二义性,所以setDessert方法在自动注入Dessert类型的组件时,不知道应该注入哪种具体类型的component,将会抛出NoUniqueBeanDefinitionException异常:

1
2
3
4
nested exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [com.desserteater.Dessert] is defined:
expected single matching bean but found 3: cake,cookies,iceCream

当然,上面的例子只是为了解释什么时候会产生二义性,在实际项目中,很少会遇到这种情况。如果在某些特殊场景遇到了,也可以使用Spring提供的方案很好的解决,具体方案如下:

使用@Primary标签指定首选bean

Spring可以对bean进行自动扫描,也可以显式定义要装配的bean,这两种情况下都可以使用@Primary标签进行标注。

自动扫描:

1
2
3
@Component
@Primary
public class IceCream implements Dessert { ... }

使用java显式定义要装配的bean:

1
2
3
4
5
@Bean
@Primary
public Dessert iceCream() {
return new IceCream();
}

使用XML显示定义bean:

1
2
3
<bean id="iceCream"
class="com.desserteater.IceCream"
primary="true" />

当有多个bean都加了@Primary标签时,这种方法其实并没有解决二义性问题,因为spring仍然会把所有加了这个@Primary标签的bean作为候选bean,所以,更好的方式是使用@Qualifier标签来给每个bean定义id。

使用@Qualifier标签装配指定id的bean

实际上@Qualifier标签有两种用法。一种是在注入bean时,和@Autowired或者@Inject合用,指定要装配哪个id的bean,比如:

1
2
3
4
5
6
//代码清单一
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

表示要注入id为iceCream的bean。

另一种用法是,在要被装配的bean上,使用@Qualifier,可以为这个bean定义id(以替代根据类名自动生成的id)。比如:

1
2
3
4
//代码清单二
@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }

定义IceCream类型的bean的id为cold,作者建议使用cold这样描述性的词汇替代类名自动声明的id,防止bean的类名进行重命名造成注入bean的地方也要跟着改。比如代码清单二中IceCream重命名为Gelato时,代码清单一中使用@Qualifier(“iceCream”)就找不到id为iceCream,就会注入失败了。当然,在不存在二义性的情况下,不需要使用@Qualifier标签来注入bean,也不用特意为类指定id。

因为使用描述性的词汇定义bean,所以很可能遇到重名的情况,比如:

1
2
3
4
//代码清单三
@Component
@Qualifier("cold")
public class Popsicle implements Dessert { ... }

这样Popsicle和IceCream的beanId都是cold,又生成二义性了,此时可以通过使用多个@Qualifier的方式进行定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//代码清单四
@Component
@Qualifier("cold")
@Qualifier("creamy")
public class IceCream implements Dessert { ... }


@Component
@Qualifier("cold")
@Qualifier("fruity")
public class Popsicle implements Dessert { ... }

@Autowired
@Qualifier("cold")
@Qualifier("creamy")
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

由于Spring的@Qualifier标签没有用@Repeatable标注,因此不具备@Repeatable的属性,所以像上面这样重复使用@Qualifier是无效的,清单四的代码要改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold { }


@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy { }

@Component
@Cold
@Creamy
public class IceCream implements Dessert { ... }

@Component
@Cold
@Fruity
public class Popsicle implements Dessert { ... }

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert) {
this.dessert = dessert;
}

新增了@Cold,@Creamy等标签替代@Qualifier。类比于html,这样用法是把id当作class使用,好处是更加灵活,但代码看起来也更复杂了一些。其实,一般情况下,使用一个@Qualifier标签就够了,用不到这种多重定义。

© 2022 谈谈IT All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero