浅聊依赖注入中的@Qualifer
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 作为注解,由处理注解的上下文进行处理,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 解析依赖时会调用 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 进行分组。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构