学而不思则罔,思而不学则殆。
应用场景
Spring使用自动装配将bean引用注入到构造参数和属性中时,仅有一个bean匹配所需的结果时,自动装配才是有效的,如果不止一个bean能够匹配,这种歧义性会阻碍Spring自动装配属性、构造器参数或方法参数,且会抛出NoUniqueBeanDefinitionException。
如下有Computer:
package chapter3;public interface Computer { void play();}
类AppleComputer,LenovoComputer,HaierComputer都实现了接口Computer,且均使用了@Component注解:
package chapter3;import org.springframework.stereotype.Component;@Componentpublic class AppleComputer implements Computer { public void play() { System.out.println("apple computer playing..."); }}
package chapter3;import org.springframework.stereotype.Component;@Componentpublic class LenovoComputer implements Computer { public void play() { System.out.println("lenovo computer playing..."); }}
package chapter3;import org.springframework.stereotype.Component;@Componentpublic class HaierComputer implements Computer { public void play() { System.out.println("haier computer playing..."); }}
在Spring试图自动装配时,Spring无法做出选择,只好宣告失败并抛出异常:
package chapter3;import org.springframework.stereotype.Component;@Componentpublic class HaierComputer implements Computer { public void play() { System.out.println("haier computer playing..."); }}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'chapter3.ComputerTest': Unsatisfied dependency expressed through method 'setComputer' parameter 0; nested exception is org.springframework.beans. factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'chapter3.Computer' available: expected single matching bean but found 3: appleComputer,haierComputer,lenovoComputer Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'chapter3. Computer' available: expected single matching bean but found 3: appleComputer,haierComputer,lenovoComputer
当遇到这种歧义性的时候,Spring提供了多种可选方案来解决这样的问题:设置首选的bean;使用限定符。
设定首选的bean
在Spring中可以通过@Primary来指定首选的bean,@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。
如下使用@Component与@Primary组合指定AppleComputer为首选的bean,那么当创建Computer引用时,Spring会使用首选的bean。
package chapter3;import org.springframework.context.annotation.Primary;import org.springframework.stereotype.Component;@Component@Primarypublic class AppleComputer implements Computer { public void play() { System.out.println("apple computer playing..."); }}
package chapter3;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;@Configuration@ComponentScanpublic class ComputerConfig { @Bean @Primary public Computer appleComputer() { return new AppleComputer(); }}
在XML中使用primary属性指定首选的bean
@Primary无法指定唯一的、无歧义性的bean,如果你在LenovoComputer或HaierComputer类上也加上@Primary,那么也带来了新的歧义性问题。
使用限定符限定自动装配的bean
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入的bean ID。
package chapter3;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=ComputerConfig.class)public class ComputerTest { private Computer computer; @Autowired @Qualifier("appleComputer") public void setComputer(Computer computer) { this.computer = computer; } @Test public void run() { computer.play(); } }
因为@Qualifier限定符与要注入的bean的名称时紧耦合的,假设修改AppleComputer类的名称,那么限定符会失效,这个时候我们可以创建自定义的限定符,而不是依赖于将bean ID作为限定条件。
package chapter3;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Component;@Component@Qualifier("apple")public class AppleComputer implements Computer { public void play() { System.out.println("apple computer playing..."); }}
package chapter3;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes=ComputerConfig.class)public class ComputerTest { private Computer computer; @Autowired @Qualifier("apple") public void setComputer(Computer computer) { this.computer = computer; } @Test public void run() { computer.play(); } }
还是存在一个问题,仅使用一个@Qualifier仍然无法确定唯一的、无歧义性的bean。
Java 8 允许出现重复的注解,只要这个注解本身在定义时带有@Repeatable注解,但是@Qualifier注解在定义时没有该定义。
* Copyright 2002-2011 the original author or authors.package org.springframework.beans.factory.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * This annotation may be used on a field or parameter as a qualifier for * candidate beans when autowiring. It may also be used to annotate other * custom annotations that can then in turn be used as qualifiers. * * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 * @see Autowired */@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Qualifier { String value() default "";}
虽然我们无法只借助@Qualifier限定符注解就确定唯一、无歧义的bean,但是,我们可以创建自定义的限定符注解,然后进行任意组合直至将可选范围缩小到只有一个bean满需需求。
创建自定义的限定符注解
创建@Apple限定符注解
package chapter3;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.beans.factory.annotation.Qualifier;@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Qualifierpublic @interface Apple {}