人人都会AI编程

3.4 依赖注入实现原理:构造器注入、Setter 注入、字段注入

更新时间:2026-07-11

在 Spring 应用中,依赖注入几乎无处不在。最基本的用法是通过构造器或字段标注 @Autowired,让容器自动提供匹配的 Bean。但当容器中存在多个同类型的 Bean 时,仅靠 @Autowired 就不够了。Spring 为此提供了 @Qualifier@Primary 两个辅助注解,帮助开发者精确控制注入的目标。理解这三者的配合关系,是写出健壮、可维护配置的关键。

3.4.1 @Autowired:自动装配的基石

@Autowired 是 Spring 中最常用的注入注解,它告诉容器:“这里需要一个 Bean,请自动帮我找到并注入。” Spring 在解析时遵循以下规则:

  1. 按类型匹配:首先在容器中查找与声明类型兼容的 Bean(包括其子类或实现类)。
  2. 限定符辅助:如果有多个同类型的 Bean,则结合 @Qualifier 指定的 Bean 名称或限定符进行筛选。
  3. 非必须性控制:可通过 required = false 将注入设为可选,如果容器中没有对应的 Bean,Spring 会跳过不报错。对于可能不存在的依赖十分有用。
  4. 装配点:可以标注在构造器、Setter 方法、字段甚至普通方法参数上。

常见用法示例

@Service
public class OrderService {

    private final PaymentService paymentService;

    // 构造器注入(推荐)
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

当容器中只有一个 PaymentService 实现类时,这段代码就能完美运行。但如果项目里有 AlipayPaymentServiceWechatPaymentService 都实现了 PaymentService,仅仅使用 @Autowired 就会导致启动失败,抛出 NoUniqueBeanDefinitionException

3.4.2 @Qualifier:按名称或限定符精准指定

当出现多个同类型 Bean 时,就需要用 @Qualifier 来明确告诉 Spring:“我就要那一个。” @Qualifier 可以配合 @Autowired 一起使用,指定 Bean 的名称或者自定义限定符。

1. 使用 Bean 名称作为限定符

默认情况下,每个 Bean 的名字就是它的类名首字母小写(如 alipayPaymentService)。可以在注入点直接通过 @Qualifier 引用该名称:

@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(@Qualifier("alipayPaymentService") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

这种方式简单直接,但有一个明显的隐患:Bean 名称通常由 Spring 自动生成,如果后续重构类名,这里的字符串可能会遗漏修改,导致运行时错误。因此更推荐用自定义限定符

2. 自定义限定符(类型安全的筛选)

在定义 Bean 时,就可以用 @Qualifier 给它打上一个有业务含义的标签:

@Component
@Qualifier("alipay")
public class AlipayPaymentService implements PaymentService { ... }

@Component
@Qualifier("wechat")
public class WechatPaymentService implements PaymentService { ... }

注入方同样使用 @Qualifier 指定这个标签:

@Service
public class OrderService {
    public OrderService(@Qualifier("alipay") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

更进一步,可以把限定符值定义为注解,实现编译期检查:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PaymentMode {
    String value();
}

然后这样使用:

@Component
@PaymentMode("alipay")
public class AlipayPaymentService implements PaymentService { ... }

@Service
public class OrderService {
    public OrderService(@PaymentMode("alipay") PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

这种方式避免了字符串硬编码的拼写错误,IDE 可以智能提示,可维护性大大增强。

3.4.3 @Primary:设定默认首选 Bean

还有一种常见的场景:大部分地方都使用某个主实现,只有少数特殊需求要用备选实现。这时不必在每个注入点加 @Qualifier,只需在主实现上标注 @Primary,将其设为同类型 Bean 中的默认选择。

@Component
@Primary
public class AlipayPaymentService implements PaymentService { ... }

@Component
public class WechatPaymentService implements PaymentService { ... }

此时,所有没有额外限定的 @Autowired PaymentService 都会注入 AlipayPaymentService。如果某个特定位置想用微信支付,再单独添加 @Qualifier 覆盖即可:

@Autowired
@Qualifier("wechatPaymentService")
private PaymentService backupPaymentService;  // 此处显式选择微信支付

@Primary@Qualifier 的优先级规则是:@Qualifier 的优先级高于 @Primary。如果注入点同时存在 @Qualifier@Primary,Spring 会优先使用 @Qualifier 指定的 Bean。这样可以实现“整体默认,局部特殊”的灵活装配。

3.4.4 实战中的选择建议

  • 基础场景:只有一个实现,直接用 @Autowired,干净简洁。
  • 多实现且需要精确控制:为每个实现定义业务语义的限定符(推荐用自定义注解),在注入点通过 @Qualifier 明确定位。
  • 多数场景用主实现:在主实现上用 @Primary,少数特殊之处用 @Qualifier 覆盖,避免到处写限定符。
  • 避免字段注入,使用构造器注入:在构造器参数上加 @Qualifier,依赖不可变且测试友好。
  • 谨慎对待 Bean 名称硬编码@Qualifier("beanName") 与类名绑定,重构风险较高;自定义限定符或自定义注解更安全。

通过这三个注解的灵活组合,Spring 为依赖注入提供了由粗到细的控制粒度,让代码在保持松耦合的同时,也能精确装配到需要的具体实现。这是 Spring 在日常开发中最实用的技能之一,掌握它,就掌握了 DI 容器的真正核心。