Spring Annotations # @Qualifier(每日一解系列)

2020-03-24 · 树下魅狐 · · 本文共2,194个字,预计阅读需要10分钟。

——每日一解系列之Spring Annotations

在上一篇 Spring Annotations #@Autowired中,介绍了使用@Autowired完成自动注入的使用方式,在默认情况下,Spring Framework是根据类型来解析@Autowired所注释的类,如果在Spring的容器中存在多个同种类型的Bean时,Spring Framework将无法完成依赖注入工作。原因是Spring Framework无法完成二选一或者N选一的工作,在此情况下,需要借助@Qualifier注解来指定依赖注入的范围。

每日一解系列文章

序言

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,将这些动物都放到动物园里进行管理:

@Component
public class Zoo {

    @Autowired
    private Animal animal;

    public void animalSpeaking(){
        animal.speak();
    }
}

最后,在main()方法中编写代码测试并观察控制台输出:

@SpringBootApplication
public 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注解指定需要进行注入的对象。

1.2 With Qualifier Annotation

接着上一小节的案例,分别创建DogZoo,CatZoo和CowZoo三个类,并使用@Qualifier指定需要注入的对象实例:

@Component
public class CatZoo {

    @Autowired
    @Qualifier(value = "cat")
    private Animal animal;

    public void animalSpeaking(){
        animal.speak();
    }
}
@Component
public class CowZoo {
    @Autowired
    @Qualifier(value = "cow")
    private Animal animal;

    public void animalSpeaking(){
        animal.speak();
    }
}
@Component
public class DogZoo {

    @Autowired
    @Qualifier(value = "dog")
    private Animal animal;

    public void animalSpeaking(){
        animal.speak();
    }
}

这样,相当于将原有的动物园进行了区域划分,不同的区域管理不同的动物,接下来,在main()方法中编写代码测试,并在控制台中观察输出结果:

@SpringBootApplication
public 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
@Documented
public @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注解限定各自所依赖的对象:

@Component
public class MonkeyZoo {

    @Autowired
    @AnimalType(value = "monkey")
    private Animal animal;

    public void monkeyShowTime(){
        animal.speak();
    }

}
@Component
public class PigZoo {

    @Autowired
    @AnimalType(value = "pig")
    private Animal animal;

    public void pigShowTime(){
        animal.speak();
    }
}

同样,在main()方法中编写代码测试上述两个类是否能够分别将Animal注入进来,并观察控制台输出:

@SpringBootApplication
public 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注解指定的一致。

@Component
public class OtherZoo {

    @Autowired
    private Animal monkey;

    public void showTime(){
        monkey.speak();
    }

}
@SpringBootApplication
public 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进行下载: