在上一篇 Spring Annotations #@Autowired中,介绍了使用@Autowired完成自动注入的使用方式,在默认情况下,Spring Framework是根据类型来解析@Autowired所注释的类,如果在Spring的容器中存在多个同种类型的Bean时,Spring Framework将无法完成依赖注入工作。原因是Spring Framework无法完成二选一或者N选一的工作,在此情况下,需要借助@Qualifier注解来指定依赖注入的范围。
Spring Annotations每日一解系列文章
序言
Qualifier[ˈkwäləˌfīər]本身有预选,筛选的意思,其与@Autowired注解一起使用,当需要执行依赖注入时,@Qualifier会限定Spring Framework需要选择注入的对象。这是一个很有用的注解,例如,当项目中一个Service接口有多个实现类时,就可以使用@Qualifier对依赖注入过程进行更详细的控制。
1.@Qualifier与@Autowired
当你的应用程序中有多个同种类型的Bean存在时,永远不要让Spring Framework自己决策在依赖注入的过程中选择哪一个Bean,如果你这样做了,将会收到来自Spring Framework的友情提示(臣妾做不到):
Cause by : org.springframework.beans.factory.NoUniqueBeanDefinitionException
假定这样一个场景,定义一个Animal接口并包含一个speak()方法,然后定义三个类Dog,Cat和Cow实现Animal中定义的speak()方法。首先,只使用@Autowired注解并从Spring容器中获取Animal看会发生什么情况。代码如下所示:
1.1 Just Autowired Annotation
首先,需要开启Spring Framework组件扫描功能:
@Configuration@ComponentScan(basePackages = {"com.ramostear.qualifier.annotation.beans"})public class AppConfig {}
接着,开始创建Animal接口和其他动物类:
public interface Animal { void speak();}
@Component("cat")public class Cat implements Animal { @Override public void speak() { System.out.println("Meow ~ Meow ~"); }}
@Component("cow")public class Cow implements Animal { @Override public void speak() { System.out.println("Moo~Moo~"); }}
@Component("dog")public class Dog implements Animal{ @Override public void speak() { System.out.println("Wang~Wang~"); }}
接下来,创建一个动物园类Zoo,将这些动物都放到动物园里进行管理:
@Componentpublic class Zoo { @Autowired private Animal animal; public void animalSpeaking(){ animal.speak(); }}
最后,在main()方法中编写代码测试并观察控制台输出:
@SpringBootApplicationpublic class QualifierAnnotationApplication { public static void main(String[] args) { SpringApplication.run(QualifierAnnotationApplication.class, args); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); Zoo zoo = context.getBean(Zoo.class); zoo.animalSpeaking(); context.close(); }}
运行上述的main()方法,控制台将输出如下信息:
***************************APPLICATION FAILED TO START***************************Description:Field animal in com.ramostear.qualifier.annotation.beans.Zoo required a single bean, but 3 were found: - cat: defined in... - cow: defined in... - dog: defined in...Action: ....or using @Qualifier to identify the bean that should be consumed
补充
这就是文章一开始提到的来自Spring Framewok的善意提示(臣妾做不到!)
如果你使用IntelliJ IDEA这类的代码编辑器,在创建Zoo类时就会提示有多个类型相同的Bean存在,Spring无法自动完成依赖注入,并推荐你使用Qualifier注解指定需要进行注入的对象。
IDEA 提示
1.2 With Qualifier Annotation
接着上一小节的案例,分别创建DogZoo,CatZoo和CowZoo三个类,并使用@Qualifier指定需要注入的对象实例:
@Componentpublic class CatZoo { @Autowired @Qualifier(value = "cat") private Animal animal; public void animalSpeaking(){ animal.speak(); }}
@Componentpublic class CowZoo { @Autowired @Qualifier(value = "cow") private Animal animal; public void animalSpeaking(){ animal.speak(); }}
@Componentpublic class DogZoo { @Autowired @Qualifier(value = "dog") private Animal animal; public void animalSpeaking(){ animal.speak(); }}
这样,相当于将原有的动物园进行了区域划分,不同的区域管理不同的动物,接下来,在main()方法中编写代码测试,并在控制台中观察输出结果:
@SpringBootApplicationpublic class QualifierAnnotationApplication { public static void main(String[] args) { SpringApplication.run(QualifierAnnotationApplication.class, args); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); DogZoo dogZoo = context.getBean(DogZoo.class); dogZoo.animalSpeaking(); CatZoo catZoo = context.getBean(CatZoo.class); catZoo.animalSpeaking(); CowZoo cowZoo = context.getBean(CowZoo.class); cowZoo.animalSpeaking(); context.close(); }}
Wang ~ Wang ~Meow ~ Meow ~Moo ~ Moo ~Process finished with exit code 0
2. 自定义Qualifier注解
除了使用Spring Framework提供的@Qualifier注解之外,还可以在@Qualifier的基础上自定义qualifier注解类。在自定义qualifier注解之前,先了解@Qualifier注解类的相关细节:
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Qualifier { String value() default "";}
Spring Framework提供的Qualifier类很简单,只有一个用于表示Spring容器中bean名称的默认值value,因此,自定义qualifier类只需要使用@Qualifier注解进行注释即可。接下来,定义一个用于表示动物类型的qualifier注解AnimalType:
@Qualifier@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface AnimalType { String value() default "";}
为了与上一节区分开,再定义两个Animal接口的实现类Monkey和Pig,并使用自定义的AnimalType注解进行注释:
@Component@AnimalType(value = "monkey")public class Monkey implements Animal{ @Override public void speak() { System.out.println("dai~ dai~"); }}
@Component@AnimalType(value = "pig")public class Pig implements Animal{ @Override public void speak() { System.out.println("heng ~ heng ~"); }}
接下来,定义两个类MonkeyZoo和PigZoo,并使用@AnimalType注解限定各自所依赖的对象:
@Componentpublic class MonkeyZoo { @Autowired @AnimalType(value = "monkey") private Animal animal; public void monkeyShowTime(){ animal.speak(); }}
@Componentpublic class PigZoo { @Autowired @AnimalType(value = "pig") private Animal animal; public void pigShowTime(){ animal.speak(); }}
同样,在main()方法中编写代码测试上述两个类是否能够分别将Animal注入进来,并观察控制台输出:
@SpringBootApplicationpublic class QualifierAnnotationApplication { public static void main(String[] args) { SpringApplication.run(QualifierAnnotationApplication.class, args); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); MonkeyZoo monkeyZoo = context.getBean(MonkeyZoo.class); monkeyZoo.monkeyShowTime(); PigZoo pigZoo = context.getBean(PigZoo.class); pigZoo.pigShowTime(); context.close(); }}
dai~ dai~heng ~ heng ~
在此小节中,演示了如何自定义并使用qualifier注解,除了将自定义qualifier注解作用于属性上,还可以将其作用于方法,对象类型以及方法参数上。
3. 根据Bean Name完成自动注入
除上述的方法之外,Spring Framework也提供了基于Bean Name完成依赖注入工作。使用此方式的前提是在类中所依赖的Bean name需要和Spring容器中所管理的Bean name保持一致。如果在使用@Component注解时指定了Bean的名称,则在引用的属性名称必须于@Component注解指定的一致。
@Componentpublic class OtherZoo { @Autowired private Animal monkey; public void showTime(){ monkey.speak(); }}
@SpringBootApplicationpublic class QualifierAnnotationApplication { public static void main(String[] args) { SpringApplication.run(QualifierAnnotationApplication.class, args); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); OtherZoo otherZoo = context.getBean(OtherZoo.class); otherZoo.showTime(); context.close(); }
dai~ dai~
综上所述,当应用程序中出现多个同种类型的Bean时,为了让Spring能够顺利完成依赖注入,使用@Qualifier注解是一个不错的选择,使用@Qualifier对依赖注入过程进行更详细的控制,防止出现二选一,多选一的情况发生。此外,文中还列举了自定义qualifier注解的定义和使用方法,以及基于Bean name完成依赖注入的演示案例。
本文所涉及的源代码已上传到Github,需要的小伙伴可点击下面的链接移步到Github进行下载:
https://github.com/ramostear/spring-annotations-daily-example