Spring 负责注入的注解详解
Spring 的注入注解分为两大体系:Spring 自有注解 (@Autowired、@Qualifier、@Primary、@Value)和 JSR 标准注解 (@Resource、@Inject、@Named)。它们共同解决同一个问题:把容器中的 Bean 或配置值注入到目标对象中。
一、注入注解总览
| 注解 | 来源 | 匹配方式 | 支持位置 | 特有属性 |
|---|---|---|---|---|
@Autowired |
Spring | 先按类型,再按名称 | 字段、构造器、Setter、参数 | required |
@Qualifier |
Spring | 按名称 | 字段、构造器、Setter、参数 | value |
@Primary |
Spring | 标记首选 | 类、方法 | --- |
@Value |
Spring | 按配置键 | 字段、构造器、Setter、参数 | value |
@Resource |
JSR-250 | 先按名称,再按类型 | 字段、Setter | name、type |
@Inject |
JSR-330 | 按类型 | 字段、构造器、方法 | --- |
@Named |
JSR-330 | 按名称 | 类、字段、参数 | value |
二、@Autowired
Spring 最核心的注入注解,默认按类型匹配。
2.1 三种注入位置
构造器注入(推荐):
java
@Service
public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
类只有一个构造方法时,@Autowired 可以省略。
字段注入:
java
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
Setter 注入:
java
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
2.2 匹配规则
- 按类型查找容器中的 Bean。
- 找到多个同类型 Bean 时,按字段名或参数名匹配 Bean 名称。
- 仍无法确定时,尝试
@Primary标记的 Bean。 - 最后用
@Qualifier指定的名称匹配。
2.3 required 属性
java
@Autowired(required = false)
private UserDao userDao; // 找不到时注入 null,不报错
默认 required = true,找不到匹配 Bean 时抛 NoSuchBeanDefinitionException。
2.4 底层处理
AutowiredAnnotationBeanPostProcessor 处理 @Autowired、@Value、@Inject。它在 postProcessProperties() 中扫描字段和方法,通过 DefaultListableBeanFactory.resolveDependency() 解析依赖并注入。
三、@Qualifier
配合 @Autowired 使用,在同类型多 Bean 时按名称指定。
java
@Service
public class OrderService {
@Autowired
@Qualifier("aliPayService")
private PaymentService paymentService;
}
构造器参数上也可以使用:
java
public OrderService(@Qualifier("aliPayService") PaymentService paymentService) {
this.paymentService = paymentService;
}
@Qualifier 的优先级高于 @Primary。
四、@Primary
同类型多 Bean 时,标记其中一个为首选,解决 @Autowired 的歧义。
java
@Service
@Primary
public class AliPayService implements PaymentService { }
@Service
public class WechatPayService implements PaymentService { }
@Service
public class OrderService {
@Autowired
private PaymentService paymentService; // 注入 AliPayService
}
@Primary 可以标注在类上,也可以标注在 @Bean 方法上:
java
@Bean
@Primary
public DataSource primaryDataSource() { ... }
五、@Value
注入简单类型和配置值,不涉及 Bean 之间的依赖。
java
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.timeout:30}")
private int timeout;
@Value("${app.debug:false}")
private boolean debug;
@Value("#{${app.timeout} * 1000}")
private int timeoutInMillis;
}
${key:default} 设置默认值,#{} 使用 SpEL 表达式。
@Value 的底层也是 AutowiredAnnotationBeanPostProcessor 处理,与 @Autowired 共用同一条注入链路。
六、@Resource
JSR-250 标准注解,由 JDK 提供,Spring 支持。默认按名称注入,找不到再按类型。
java
@Service
public class UserService {
@Resource(name = "userDaoImpl")
private UserDao userDao;
}
不指定 name 时,默认使用字段名作为 Bean 名称:
java
@Resource
private UserDao userDao; // 按名称 "userDao" 查找
@Resource 与 @Autowired 的区别
| 对比维度 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring | JSR-250(JDK) |
| 匹配方式 | 先按类型,再按名称 | 先按名称,再按类型 |
| required 属性 | 支持 | 不支持 |
| 构造器注入 | 支持 | 不支持 |
| @Primary 支持 | 支持 | 不支持 |
| 适用场景 | Spring 生态通用 | 按名称注入、非 Spring 环境 |
@Resource 由 CommonAnnotationBeanPostProcessor 处理。
七、@Inject 和 @Named
JSR-330 标准注解,需要引入 javax.inject 或 jakarta.inject 依赖。
xml
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
@Inject
按类型注入,语义与 @Autowired 基本一致,但没有 required 属性。
java
@Service
public class UserService {
@Inject
private UserDao userDao;
}
@Named
配合 @Inject 按名称指定:
java
@Service
public class OrderService {
@Inject
@Named("aliPayService")
private PaymentService paymentService;
}
也可以标注在类上指定 Bean 名称:
java
@Named("aliPayService")
public class AliPayService implements PaymentService { }
@Inject 和 @Named 同样由 AutowiredAnnotationBeanPostProcessor 处理,Spring 对它们提供了完整支持。
八、注入注解的底层处理链
所有注入注解最终都汇聚到两个 BeanPostProcessor:
AutowiredAnnotationBeanPostProcessor
├── @Autowired
├── @Value
└── @Inject
CommonAnnotationBeanPostProcessor
├── @Resource
├── @PostConstruct
└── @PreDestroy
处理时机在 Bean 生命周期的属性填充阶段 (populateBean()),由 postProcessProperties() 方法完成注入。
简化流程:
java
// AutowiredAnnotationBeanPostProcessor
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 1. 扫描字段和方法上的 @Autowired、@Value、@Inject
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
// 2. 逐个解析依赖并注入
metadata.inject(bean, beanName, pvs);
return pvs;
}
metadata.inject() 内部通过 DefaultListableBeanFactory.resolveDependency() 查找匹配的 Bean,再通过反射设置字段值或调用方法。
九、四种注入方式对比
| 对比维度 | 构造器注入 | Setter 注入 | 字段注入 | 方法参数注入 |
|---|---|---|---|---|
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 依赖不可变 | ✅ final | ❌ | ❌ | ✅ |
| 空指针风险 | 无 | 有 | 有 | 无 |
| 单元测试 | 容易 | 中等 | 困难 | 容易 |
| 循环依赖 | 不支持 | 支持 | 支持 | 不支持 |
| Spring 官方推荐 | ✅ | ⚠️ | ❌ | ✅ |
Spring 官方推荐构造器注入,原因:
- 依赖不可变,用
final修饰 - 对象创建时依赖完全就绪
- 便于单元测试,直接传入 Mock
- 依赖过多时构造方法参数过长,能及时暴露设计问题
十、常见问题
1. 同类型多 Bean 注入失败
NoUniqueBeanDefinitionException: expected single matching bean but found 2
解决方案:用 @Qualifier 指定名称,或用 @Primary 标记首选。
2. @Autowired 注入 null
- 检查类是否被 Spring 管理
- 检查包是否在
@ComponentScan范围内 - 检查是否设置了
required = false
3. 构造器注入循环依赖
java
@Service
public class AService {
public AService(BService b) { }
}
@Service
public class BService {
public BService(AService a) { }
}
启动时报 BeanCurrentlyInCreationException。构造器注入无法解决循环依赖,应通过重构消除,或改用字段注入。
4. @Resource 找不到 Bean
@Resource 默认按名称匹配,字段名必须与 Bean 名称一致。不一致时需指定 name。
5. @Value 注入失败
Could not resolve placeholder 'app.name'
检查配置文件中是否有对应的键,或设置默认值 ${app.name:default}。
十一、总结
| 维度 | 核心要点 |
|---|---|
| 核心注解 | @Autowired(按类型)、@Resource(按名称)、@Inject(按类型) |
| 辅助注解 | @Qualifier(指定名称)、@Primary(标记首选) |
| 配置注入 | @Value(简单类型和配置值) |
| 推荐方式 | 构造器注入 |
| 底层处理 | AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor |
| 处理时机 | Bean 生命周期的属性填充阶段 |
| 匹配优先级 | @Qualifier > @Primary > 按类型 > 按名称 |
Spring 的注入注解体系围绕"按类型"和"按名称"两条主线展开。@Autowired 和 @Inject 默认按类型,@Resource 和 @Named 默认按名称,@Qualifier 和 @Primary 用于解决歧义。理解每个注解的匹配规则和底层处理机制,能帮你在实际开发中做出正确的选择,避免注入失败和循环依赖等问题。