前言
Spring 支持注入单一类型和集合类型的依赖,对于单一类型,如果按照类型进行注入,容器中存在多个相同类型的 bean 时,Spring 将抛出 NoUniqueBeanDefinitionException 异常。对于这种情况,我们可以选择将某一个 bean 设置为 primary,然而如果存在多个 primary 的 bean,Spring 仍将无法处理,这时便引出我们今天介绍的 @Qualifier,使用 @Qualifier 可以明确指出注入哪个 bean。
@Qualifier 注解的使用
@Qualifer 注解通常有两种用法。
依赖注入单一类型的 bean 时显式指出依赖的 bean 的名称,避免存在多个类型相同的 bean 而抛出异常。
依赖注入集合类型时为依赖进行分组。
注入单一类型 bean 的示例如下。
public class App { @Qualifier("bean1") // ① @Autowired private String bean; public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(App.class); context.refresh(); System.out.println(context.getBean(App.class).bean); context.close(); } // @Qualifier("bean1") ② @Bean public String bean1() { return "bean1"; } @Bean public String bean2() { return "bean2"; } }
上述示例,容器中注册了两个类型为 String 的bean,在注入依赖时,使用 @Qualifier 指出需要注入 bean 的名称为 bean1,从而避免了抛出异常。注意此时等同于与在 bean1 上加入 @Qualifier("bean1") ,代码中位置①和位置②同时修改为 @Qualifier("bean") 也可以达到相同的目的。
使用 @Qualifier 为集合类型的依赖分组的示例如下。
public class App { @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Qualifier public static @interface MyQualifierGroup{ } @Autowired private List<String> bean12; @Qualifier @Autowired private List<String> bean34; @MyQualifierGroup @Autowired private List<String> bean56; public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(App.class); context.refresh(); App app = context.getBean(App.class); System.out.println("bean12: "+app.bean12); System.out.println("bean34: "+app.bean34); System.out.println("bean56: "+app.bean56); context.close(); } @Bean public String bean1() { return "bean1"; } @Bean public String bean2() { return "bean2"; } @Qualifier @Bean public String bean3() { return "bean3"; } @Qualifier @Bean public String bean4() { return "bean4"; } @MyQualifierGroup @Bean public String bean5() { return "bean5"; } @MyQualifierGroup @Bean public String bean6() { return "bean6"; } }
上述示例中,在 Spring 容器中注册了6个 String 类型的 bean,其中 bean1,bean2 上没有加 @Qualifier ,bean3,bean4 上加了 @Qualifier 注解,bean5,bean6 上加了自定义的使用 @Qualifier 标注的注解 @MyQualifierGroup,同时在类型为 App 的 bean 中注入了三个 List<String> 类型的依赖,分别不加 @Qualifier,添加 @Qualifier,添加 @MyQualifierGroup 注解,打印结果如下所示。
bean12: [bean1, bean2, bean3, bean4, bean5, bean6] bean34: [bean3, bean4, bean5, bean6] bean56: [bean5, bean6]
不加 @Qualifier 注解,注入了所需类型的所有 bean,而加了 @Qualifier 注解后依赖上注解必须和我们指定的 @Qualifier 类型一致才会注入。
@Qualifier 实现简单分析
@Qualifier 作为注解,由处理注解的上下文进行处理,《掌握 Spring 必须知道的 BeanDefinition》 一文中曾提到,AnnotatedBeanDefinitionReader 会将注解信息读取为 BeanDefinition,AnnotatedBeanDefinitionReader 构造方法如下。
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); this.registry = registry; this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); // 注册处理注解的处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); }
实例化时,AnnotatedBeanDefinitionReader 会调用AnnotationConfigUtils#registerAnnotationConfigProcessors
向 Spring 注册一些处理注解的 BeanPostProcessor,跟踪源码如下。
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { // 注册自动注入的候选项解析器 beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } ... 省略部分代码 }
注册 BeanPostProcessor 时 Spring 会先注册自动注入的候选项解析器 ContextAnnotationAutowireCandidateResolver,重点就在这个解析器中。在文章《浅析 Spring 依赖解析实现》 中提到,Spring 解析依赖时会调用 DefaultListableBeanFactory#isAutowireCandidate 判断给定类型的 bean 是否为依赖的候选项,跟踪源码如下。
protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd, DependencyDescriptor descriptor, AutowireCandidateResolver resolver) { String beanDefinitionName = BeanFactoryUtils.transformedBeanName(beanName); resolveBeanClass(mbd, beanDefinitionName); if (mbd.isFactoryMethodUnique && mbd.factoryMethodToIntrospect == null) { new ConstructorResolver(this).resolveFactoryMethodIfPossible(mbd); } BeanDefinitionHolder holder = (beanName.equals(beanDefinitionName) ? this.mergedBeanDefinitionHolders.computeIfAbsent(beanName, key -> new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName))) : new BeanDefinitionHolder(mbd, beanName, getAliases(beanDefinitionName))); // 解析给定的 bean 是否为自动注入的候选项 return resolver.isAutowireCandidate(holder, descriptor); }
这里正是使用到了上面设置的 ContextAnnotationAutowireCandidateResolver,这个类会将 bean 上的 @Qualifier 和依赖描述符 DependencyDescriptor 中的 @Qualifier 信息进行匹配,从而对 bean 进行分组。
总结
@Qualifier 作为 Spring 官方提供的注解,在注入单一类型的 bean 时可以指定 bean 的名称,此时 @Autowired + @Qualifier = @Resource,同时在注入集合类型的 bean 时 @Qualifier 还可以为 bean 分组。