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标签就够了,用不到这种多重定义。