《Spring实战》-第三章(Bean的高级装配(3)-处理自动装配Bean的歧义性)

慢慢来比较快,虚心学技术
1、什么是自动装配Bean的歧义性 【《Spring实战》-第三章(Bean的高级装配(3)-处理自动装配Bean的歧义性)】当一个接口存在多个实现类或一个类存在多个bena定义的时候,如果使用自动装配,Spring将无法明确应该使用哪一个Bean进行装配注入,这时候就会报 NoUniqueBeanDefinitionException 的异常,这就是自动装配Bean的歧义性
如现有一个接口BaseService,该接口有两个实现类:BaseServiceImpl1和BaseServiceImpl2,还有一个基本类BaseBean
//基本类 @Component public class BaseBean { private String name="BaseBean"; public void setName(String name){ this.name = name; } }//接口 public interface BaseService { void update(); }//接口实现类1 @Component public class BaseServiceImpl1 implements BaseService {@Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl1"+baseBean.getName()); } }//接口实现类2 @Component public class BaseServiceImpl2 implements BaseService { @Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl2"+baseBean.getName()); } }//测试类: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {ComponentConfig.class}) public class AppComponentTest {@Autowired private ApplicationContext applicationContext; //注入baseService @Autowired private BaseService baseService; @Test public void testPrimary(){ baseService.update(); } }//测试结果:注入失败,BaseService不止有一个实现类 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.my.spring.service.BaseService' available: expected single matching bean but found 2: baseServiceImpl1,baseServiceImpl2

2、怎么处理自动装配Bean的歧义性 当应用内的接口存在多个实现类或一个类存在多个bean定义,我们有两个处理方式:
Ⅰ、标识首选Bean
当存在歧义Bean的时候,我们可以使用 @Primary注解指定首选Bean,告诉Spring当发生歧义的时候,优先使用该Bean
如上述代码将BaseServiceImpl1设定为首选Bean
@Component @Primary public class BaseServiceImpl1 implements BaseService {@Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl1"+baseBean.getName()); } }

此时运行测试结果表明注入成功
BaseServiceImpl1BaseBean

那么问题来了,假如使用xml配置如何设置首选Bean?
假设BaseBean使用xml配置,我们可以通过设置节点的primary属性为true将其设置为首选bean

Ⅱ、自定义特殊标识
设置首选 bean 的局限性在于 @Primary 无法将可选方案的范围限定到唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首选bean 的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。
此时我们需要通过自定义特殊标识即Spring限定符@Qualifier来为每一个Bean指定,如我们可以为上述BaseServiceImpl1和BaseServiceImpl2分别加上特殊标识,然后结合@Autowired注解进行特定注入
//接口实现类1 @Component @Qualifier("BaseService1") public class BaseServiceImpl1 implements BaseService {@Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl1"+baseBean.getName()); } }//接口实现类2 @Component @Qualifier("BaseService2") public class BaseServiceImpl2 implements BaseService { @Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl2"+baseBean.getName()); } }//测试类: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {ComponentConfig.class}) public class AppComponentTest {@Autowired private ApplicationContext applicationContext; //注入baseService(限定词为BaseService1) @Autowired @Qualifier("BaseService1") private BaseService baseService; @Test public void testPrimary(){ baseService.update(); } }测试结果: BaseServiceImpl1BaseBean

实际上,我们甚至可以自定义限定符注解(当在一个bean或方法中使用多个@Qualifier注解时会编译报错),而使用自定义限定符注解和@Qualifier注解效果一致,我们需要做的只是创建一个注解,并使用@Qualifier对该注解进行标识,如我们可以将上述的BaseServiceImpl1和BaseServiceImpl2的限定符注解替换成如下样式:
//创建注解@BaseService1 @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface BaseService1 { }//在BaseServiceImpl1上使用@BaseService1 @Component @BaseService1//实际上相当于@Qualifier("BaseService1") public class BaseServiceImpl1 implements BaseService { @Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl1"+baseBean.getName()); } }//创建注解@BaseService2 @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface BaseService2 { }//在BaseServiceImpl2上使用@BaseServiceImpl2 @Component @BaseService2//实际上相当于@Qualifier("BaseService2") public class BaseServiceImpl2 implements BaseService { @Autowired private BaseBean baseBean; @Override public void update() { System.out.println("BaseServiceImpl2"+baseBean.getName()); } }//测试类注入BaseServiceImpl1 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader = AnnotationConfigContextLoader.class,classes = {ComponentConfig.class}) public class AppComponentTest {@Autowired private ApplicationContext applicationContext; //注入baseService(限定词为BaseService1) @Autowired @BaseService1//实际上相当于@Qualifier("BaseService1") private BaseService baseService; @Test public void testPrimary(){ baseService.update(); } }测试结果: BaseServiceImpl1BaseBean

注:其中
  1. @Target限定注解可以使用的范围:ElementType.CONSTRUCTOR(构造函数),ElementType.FIELD(字段),ElementType.METHOD(方法),ElementType.TYPE(类,接口等)
  2. @Retention限定了该Annotation被保留的时间长短:RetentionPolicy.SOURCE(在源文件中有效,即源文件保留),RetentionPolicy.CLASS(在class文件中有效,即class保留),RetentionPolicy.RUNTIME(在运行时有效,即运行时保留)

    推荐阅读