1、什么是Bean在自动装配时的歧义性

Spring在自动装配的过程中,如果不仅有一个Bean满足装配条件时,这种歧义会阻碍Spring自动装配属性,构造器参数或者方法参数。例如,我们定义一个接口为Music,再定义该接口的三个实现类分别为:FashionMusic、ClassicalMusic、RapMusic代码如下:

public interface Music {
   public String  getMusicType();
}
@Component
public class ClassicalMusic implements Music {
    @Override
    public String getMusicType() {
        return "古典音乐";
    }
}
@Component
public class FashionMusic implements Music {
    @Override
    public String getMusicType() {
        return "流行音乐";
    }
}
@Component
public class RapMusic implements Music {
    @Override
    public String getMusicType() {
        return "说唱音乐";
    }
}

下面定义一个Mp3,并使用@Autowired注入Music:

@Component
public class Mp3 {
    private Music music;
    @Autowired
    public Mp3(Music music) {
        this.music = music;
    }
    public String getMuiscType() {
        return music.getMusicType();
    }
}

定义配置类:

@Configuration
@ComponentScan("com.icypt.learn")
public class GlobalConfig {
}

由配置类可知,在Spring容器加载的时候,会扫描“com.icypt.learn”包下的所有组件Bean,但是由于有三个类都实现了Music这个接口,所以Spring在自动装配Mp3类时,无法选择要注入的是哪一个实现类,产生歧义。
定义测试类:

public class TestAmbiguity {
    public static Logger logger = LoggerFactory.getLogger(TestAmbiguity.class);
public static AnnotationConfigApplicationContext acac = new 
AnnotationConfigApplicationContext(GlobalConfig.class);
    @Test
    public void testAmbiguity() {
        if(acac.containsBean("mp3")) {
            Mp3 mp3 = acac.getBean("mp3", Mp3.class);
            logger.info(mp3.getMuiscType());
        } else {
            logger.info("mp3 bean创建失败!");
        }
    }
}

运行测试类:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.icypt.learn.service.Music' available: expected single matching bean but found 3: classicalMusic,fashionMusic,rapMusic

由运行结果产生的异常可知,已经发生歧义:容器期望的Music是一个单例的Bean,但是扫描的过程中发现有三个实例,导致无法选择产生歧义,导致装配异常。

2、如何解决Bean在自动装配时的歧义性

2.1、使用@Primary注解表明首选Bean

我们可以使用@Primary这个注解来指定首选的Bean,当容器发现有多个相同的实例时,会首选有@Primary标记的Bean进行注入,假如我们定义

FashionMusic为首选类,代码如下:

@Component
@Primary
public class FashionMusic implements Music {
    @Override
    public String getMusicType() {
        return "流行音乐";
    }
}

运行测试类:

11:48:56,763 DEBUG main support.DefaultListableBeanFactory:777 - Autowiring by type from bean name 'mp3' via constructor to bean named 'fashionMusic'
11:48:56,766 DEBUG main support.DefaultListableBeanFactory:213 - Creating shared instance of singleton bean 'classicalMusic'
11:48:56,766 DEBUG main support.DefaultListableBeanFactory:213 - Creating shared instance of singleton bean 'rapMusic'
11:48:56,800  INFO main learn.TestAmbiguity:28 - 流行音乐

由运行结果可知,自动装配正常,通过Mp3构造器注入的Music的实例正是被@Primary标记的FashionMusic组件Bean,歧义已解决。
@Primary在JavaConfig中的使用:

@Bean
@Primary
public FashionMusic fashionMusic() {
	return new FashionMusic();
}

@Primary在XmlConfig中的使用:

<bean id="fashionMusic" class="com.icypt.learn.service.impl.FashionMusic" primary="true"/>

通过@Primary确实可以解决自动装配时的歧义,但是以上的三个实现类假如有两个被定义为首选,这时岂不又出现了新的歧义,这种歧义情况如何来解决呢?

2.2 使用@Qualifier限定自动装配的Bean

通过以上的探讨可知@Primary只是定义了Bean在自动装配时的一个优先级,如果优先级别相同则还是会产生歧义,这时就需要使用Spring提供的限定符策略,它可以对需要注入的Bean进行逐层筛选,最终选出唯一一个能满足限定条件的Bean。
@Qualifier默认使用
使用Spring声明的组件Bean默认都会有一个限定符,这个限定符的标识就是BeanID,也就是该Bean的类名称首字母小写之后的值。

@Autowired
@Qualifier("fashionMusic")
public void setMusic(Music music) {
	this.music = music;
}

这里我们需要通过setter方法注入一个Music的实例,并通过@Qualifier(“fashionMusic”)限定只能注入限定符为“fashionMusic”的Bean组件。
这种使用限定符的方式将限定符标识与Bean的类名称紧密耦合,一旦我们的类名称发生变化,则必须相应修改依赖方的限定符标识。当然我们也可以为被依赖方这个Bean自定义一个修饰符,然后在依赖方使用自定义的修饰符进行限定,这样就完全将类名称与限定符标识完全解耦了。
@Qualifier自定义限定符使用

@Component
@Primary
@Qualifier("fashion")
public class FashionMusic implements Music {
    @Override
    public String getMusicType() {
        return "流行音乐";
    }
}

@Autowired
@Qualifier("fashion")
public void setMusic(Music music) {
	this.music = music;
}

通过这种自定义限定符的方式来解决自动装配时产生的歧义安全性、扩展性更高,但是有个问题,假如RapMusic也自定义了和FashionMusic相同的限定符标识,这样还是会产生新的歧义,那么如何解决呢?

通常我们想到的肯定是继续使用@ Qualifier定义新的限定符标识,将范围限定至最小,但是Java不容许在同一个条目上出现多个相同类型的注解,此时我们就需要自定义限定符注解,来解决这种歧义问题
自定义限定符注解的使用

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Say {
}

@Component
@Primary
@Qualifier("fashion")
@Say
public class RapMusic implements Music {
    @Override
    public String getMusicType() {
        return "说唱音乐";
    }
}

@Autowired
@Qualifier("fashion")
@Say
public void setMusic(Music music) {
	this.music = music;
}

可见通过自定义限定符注解就可以解决由自定义限定符标识相同而导致的歧义性问题,至此有关Bean在自动装配过程中产生的歧义性问题已探讨完毕,下次我们来探讨Bean的作用域。