解决自动装配的歧义

@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里。欢迎大家继续阅读,谢谢大家!

返回目录    下载代码

上一篇:Rust引用与借用


下一篇:使用 Certbot 申请 Let's Encrypt SSL 证书,并定时续期