乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@Qualifier 详解

定义与作用

@Qualifier 是 Spring 提供的**按名称精确限定(Name-based Qualification)注解,专门用于解决 @Autowired 按类型装配时产生的歧义(Ambiguity)**问题。

当 Spring 容器中存在多个同类型的 Bean 时,@Autowired 单独使用会抛出 NoUniqueBeanDefinitionException。@Qualifier 的作用就是在类型匹配的基础上,进一步通过 Bean 的名称(或自定义限定符)精确指定"我要的是哪一个"。

在飞翔科技的学生管理系统中,小崔需要对接两个数据源:一个主库 masterDataSource 用于读写业务数据,一个从库 slaveDataSource 用于报表查询。两个数据源都是 javax.sql.DataSource 类型,如果没有 @Qualifier,@Autowired DataSource 会让 Spring 无所适从。

适用位置与常用属性

属性类型说明
valueString指定要注入的 Bean 名称或自定义限定符标识

适用位置:

  • 构造器参数
  • 字段(Field)
  • Setter 方法参数
  • 自定义注解上(作为元注解构建组合限定符)

核心原理

@Qualifier 本身不参与 Bean 的创建,它只在**依赖解析(Dependency Resolution)**阶段发挥作用。当 AutowiredAnnotationBeanPostProcessor 发现某个注入点有 @Qualifier 时,会在类型匹配后的候选集合中,进一步筛选出名称(或限定符元数据)与 @Qualifier 的 value 属性一致的 Bean。

完整示例

场景简述

飞翔科技的学生管理系统需要同时支持 MySQL 主从架构。架构师白歌要求:业务写操作走主库,统计报表读操作走从库。小崔在配置类中定义了两个 DataSource Bean,需要在 StudentService 和 ReportService 中分别注入正确的数据源。

操作前:无 @Qualifier 导致的歧义报错

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource masterDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://master.learnto.cn:3306/student_db");
        return new HikariDataSource(config);
    }

    @Bean
    public DataSource slaveDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://slave.learnto.cn:3306/student_db");
        return new HikariDataSource(config);
    }
}
@Service
public class StudentService {

    @Autowired
    private DataSource dataSource; // 报错!容器中有两个 DataSource
}

启动报错:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
  No qualifying bean of type 'javax.sql.DataSource' available:
  expected single matching bean but found 2: masterDataSource, slaveDataSource

使用该注解的完整代码

package com.feixiang.student.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource masterDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://master.learnto.cn:3306/student_db");
        config.setUsername("write_user");
        config.setPassword("write_pass");
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }

    @Bean
    public DataSource slaveDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://slave.learnto.cn:3306/student_db");
        config.setUsername("read_user");
        config.setPassword("read_pass");
        config.setMaximumPoolSize(50);
        return new HikariDataSource(config);
    }
}
package com.feixiang.student.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;

@Service
public class StudentService {

    private final DataSource masterDataSource;

    // 构造器参数上使用 @Qualifier
    @Autowired
    public StudentService(@Qualifier("masterDataSource") DataSource masterDataSource) {
        this.masterDataSource = masterDataSource;
    }

    public void updateStudentName(Long id, String name) {
        // 通过主库执行写操作
        // ...
    }
}
package com.feixiang.student.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;

@Service
public class ReportService {

    @Autowired
    @Qualifier("slaveDataSource")
    private DataSource slaveDataSource;

    public int countTotalStudents() {
        // 通过从库执行统计查询,减轻主库压力
        // ...
        return 1000;
    }
}
package com.feixiang.student;

import com.feixiang.student.config.DataSourceConfig;
import com.feixiang.student.service.ReportService;
import com.feixiang.student.service.StudentService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class StudentApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(DataSourceConfig.class);

        // 扫描并注册 service 包
        ctx.scan("com.feixiang.student.service");
        ctx.refresh();

        StudentService studentService = ctx.getBean(StudentService.class);
        ReportService reportService = ctx.getBean(ReportService.class);

        System.out.println("StudentService 注入的数据源:" + studentService.getClass().getSimpleName());
        System.out.println("ReportService 查询结果:" + reportService.countTotalStudents());

        ctx.close();
    }
}

操作后运行结果及分析

StudentService 注入的数据源:StudentService
ReportService 查询结果:1000

变化分析:

  • StudentService 明确持有 masterDataSource,所有写操作(更新学生信息、插入选课记录)都路由到主库,保证数据一致性
  • ReportService 明确持有 slaveDataSource,复杂的统计查询(全校学生人数、各学院分布)走从库,避免拖垮主库
  • 如果未来增加更多数据源(如归档库),只需新增 @Bean 并在消费端用 @Qualifier 指定即可,扩展性良好

自定义限定符注解(高级用法)

当项目中大量使用字符串形式的 @Qualifier("xxx") 时,拼写错误只能在运行期暴露。Spring 支持将 @Qualifier 作为元注解定义类型安全的组合注解。

package com.feixiang.student.qualifier;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Master {
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Slave {
}

使用自定义限定符:

@Configuration
public class DataSourceConfig {

    @Bean
    @Master
    public DataSource masterDataSource() {
        return new HikariDataSource();
    }

    @Bean
    @Slave
    public DataSource slaveDataSource() {
        return new HikariDataSource();
    }
}
@Service
public class StudentService {

    private final DataSource dataSource;

    @Autowired
    public StudentService(@Master DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

优势:

  • 编译期检查:拼写错误会被 IDE 和编译器立即发现
  • 语义清晰:@Master 比 @Qualifier("masterDataSource") 更直观
  • 重构友好:重命名注解类即可全局替换,无需搜索字符串

易错场景与面试考点

易错场景一:@Qualifier 的 value 与 Bean 名称不匹配

@Bean
public DataSource masterDataSource() { ... }

@Service
public class StudentService {
    @Autowired
    @Qualifier("master") // 错误!Bean 名称是 masterDataSource,不是 master
    private DataSource dataSource;
}

后果:启动报错 NoSuchBeanDefinitionException: No bean named 'master' available。

正确做法:@Qualifier 的 value 必须与 @Bean 方法名(或 @Bean(name = "xxx"))完全一致,包括大小写。

易错场景二:试图用 @Qualifier 解决不同类型的问题

@Bean
public String schoolName() { return "乐途大学"; }

@Service
public class StudentService {
    @Autowired
    @Qualifier("schoolName")
    private DataSource dataSource; // 类型完全不匹配!
}

后果:即使名称匹配,Spring 也会先检查类型。String 和 DataSource 类型不兼容,仍会报错。

正确理解:@Qualifier 是在类型匹配成功后的候选集合中进行名称筛选,不能跨越类型边界。

面试考点

Q:@Qualifier 和 @Primary 有什么区别?

@Qualifier 是在消费端精确指定要注入哪一个 Bean,粒度细、意图明确。@Primary 是在定义端标记首选 Bean,当存在多个候选时优先注入被标记的。两者可以共存:@Primary 作为全局默认值,@Qualifier 作为局部覆盖。

Q:@Qualifier 可以用在哪些位置?

构造器参数、字段、Setter 方法参数,以及作为元注解定义自定义限定符注解。不能单独用在类上(类上应使用 @Component("name") 或 @Bean(name = "xxx") 定义名称)。

Q:如果 @Qualifier 指定了名称但容器中不存在该 Bean,会发生什么?

抛出 NoSuchBeanDefinitionException,即使存在其他同类型的 Bean 也不会回退注入。这与 @Resource 的行为不同——@Resource 按名称找不到时会尝试按类型匹配。

Q:@Qualifier 的 value 是否支持 SpEL 表达式?

不支持。@Qualifier 的 value 必须是静态字符串常量,在编译期即可确定。如果需要动态决定注入哪个 Bean,应使用 BeanFactory 编程式查找,或结合 @Value 与 ObjectProvider。

上一页
@Autowired 详解
下一页
@Primary 详解