@Autowired注解能让Spring容器找到类型匹配的Bean之后自动进行装配。同时,这也引出这样一个问题:“假如Spring容器存在多个类型相同的Bean,Spring容器怎么知道应该自动装配哪个Bean呢?”举个例子,假如com.dream包现有这样一些类:
1 public interface Music { 2 }
1 @Component 2 public class PopMusic implements Music { 3 }
1 @Component 2 public class ClassicMusic implements Music { 3 }
1 @Component 2 public class CountryMusic implements Music { 3 }
1 @Component 2 public class Player { 3 private Music music = null; 4 5 @Autowired 6 public Player(Music music) { 7 this.music = music; 8 } 9 }
这些代码定义了一个Music接口;三个实现了Music接口的PopMusic类,ClassicMusic类,CountryMusic类;一个Player类。其中,PopMusic类,ClassicMusic类,CountryMusic类,Player类是组件,能被组件扫描发现之后进行创建。另外,Player构造函数带有@Autowired注解,能够自动装配Music类型的Bean。可是,当我们觉得万事具备,开始运行时却发现程序抛出NoUniqueBeanDefinitionException类型的异常,根本无法完成Bean的创建与装配。
这是怎么回事呢?
原来,Spring容器创建Player时,发现Spring容器存在PopMusic,ClassicMusic,CountryMusic三个Music类型的Bean,一时之间不知如何选择,于是抛出NoUniqueBeanDefinitionException类型的异常。
那该怎么办呢?
一种解决方法是引入@Primary注解,把某个Bean标为首选。这样,当类型相同的Bean多于一个时,Spring容器就知道应该选用首选的那个进行装配了。因此,如果我们想让Spring容器选用PopMusic,可把PopMusic标为首选,如下所示:
1 @Primary 2 @Component 3 public class PopMusic implements Music { 4 }
现在,PopMusic带有@Primary注解。这样,Spring容器装配Player时,就知道该在PopMusic,ClassicMusic,CountryMusic三个Music类型的Bean里选用PopMusic了。
还有一种情况。假如现有两种音乐播放器:一种是DVD播放器;一种是数字播放器。定义如下:
1 @Component 2 public class DvdPlayer { 3 private Music music = null; 4 5 @Autowired 6 public DvdPlayer(Music music) { 7 this.music = music; 8 } 9 }
1 @Component 2 public class DigitalPlayer { 3 private Music music = null; 4 5 @Autowired 6 public DigitalPlayer(Music music) { 7 this.music = music; 8 } 9 }
可以看到DvdPlayer,DigitalPlayer的构造函数参数都是Music类型的。如果我们希望DvdPlayer播放的是PopMusic,DigitalPlayer播放的是ClassicMusic。这时,该怎么办呢?
毫无疑问,@Primary注解只能标注一个首选,而且Spring容器只会选用那个标为首选的Bean进行装配。这意味着光靠@Primary注解根本无法达成我们的目的。
那该怎么办呢?
直觉告诉我们,Spring还提供了其它方式用于解决自动装配存在的歧义问题。事实也确实如此。@Qualifier注解就是Spring提供的另一种方式。因此,我们可以使用@Qualifier注解这样解决问题:
1 @Component 2 @Qualifier("popMusicQualifier") 3 public class PopMusic implements Music { 4 }
1 @Component 2 public class DvdPlayer { 3 private Music music = null; 4 5 @Autowired 6 public DvdPlayer(@Qualifier("popMusicQualifier") Music music) { 7 this.music = music; 8 } 9 }
1 @Component 2 @Qualifier("classicMusicQualifier") 3 public class ClassicMusic implements Music { 4 }
@Component public class DigitalPlayer { private Music music = null; @Autowired public DigitalPlayer(@Qualifier("classicMusicQualifier") Music music) { this.music = music; } }
可以看到@Qualifier注解能以限定符的方式指定需要注入的Bean,具体如下:
1.添加@Qualifier注解到Bean上,指定Bean的限定符为某值
2.添加@Qualifier注解到需要注入依赖的参数旁边,指定即将注入的Bean须是限定符为某值的Bean
这样之后,Bean与Bean的注入就通过限定符关联起来了。自然而然的,Spring容器也就知道怎样通过限定符找到匹配的Bean进行自动装配了。
于是,我们在PopMusic类上添加了@Qualifier("popMusicQualifier")注解,在DvdPlayer的构造函数的参数旁边也添加了@Qualifier("popMusicQualifier")注解,从而告诉Spring容器把PopMusic注入DvdPlayer的构造函数中;我们在ClassicMusic类上添加了@Qualifier("classicMusicQualifier")注解,在DigitalPlayer的构造函数参数旁边也添加了@Qualifier("classicMusicQualifier")注解,从而告诉Spring容器把ClassicMusic注入DigitalPlayer的构造函数中。
还有,Bean的限定符默认是Bean的ID。我们知道添加@Component注解之后,Spring容器创建的Bean其ID默认是类名的第一个字母变成小写之后的字符串。也就是说,PopMusic这个Bean的ID是popMusic,ClassicMusic这个Bean的ID是classicMusic。因此,我们还能把@Qualifier注解从PopMusic类和ClassicMusic类上删掉,而后指定DvdPlayer,DigitalPlayer的构造函数参数旁边的@Qualifier注解的限定符为Bean的ID。如下所示:
1 @Component 2 public class DvdPlayer { 3 private Music music = null; 4 5 @Autowired 6 public DvdPlayer(@Qualifier("popMusic") Music music) { 7 this.music = music; 8 } 9 }
1 @Component 2 public class DigitalPlayer { 3 private Music music = null; 4 5 @Autowired 6 public DigitalPlayer(@Qualifier("classicMusic") Music music) { 7 this.music = music; 8 } 9 }
于是,关于自动装配的歧义问题,我们已经知道怎么解决了。下章该谈谈属性占位符,看看怎样把属性文件里的值读取出来之后通过@Value注解注入Bean里。欢迎大家继续阅读,谢谢大家!