@Resource 详解
定义与作用
@Resource 是 JSR-250 标准定义的通用资源注入注解,由 Java EE 规范提出,Spring 从 2.5 版本开始完整支持。与 Spring 专有的 @Autowired 不同,@Resource 默认**按名称(byName)装配依赖,如果按名称找不到匹配的 Bean,则回退为按类型(byType)**装配。
这一行为差异使得 @Resource 在需要精确控制 Bean 名称的场景下更加直观。在飞翔科技的学生管理系统中,运维工程师李眉偏好使用标准注解而非 Spring 专有注解,因为标准注解让代码更容易在不同 Java 容器间迁移。小崔在维护旧模块时,经常遇到用 @Resource 注入数据源、消息队列连接工厂等基础设施的场景。
适用位置与常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
name | String | 指定要注入的 Bean 名称,默认取字段名或 Setter 方法名推导 |
type | Class<?> | 指定要注入的 Bean 类型,通常不需要显式设置 |
lookup | String | JNDI 查找名称,Spring 环境下很少使用 |
authenticationType | AuthenticationType | 安全认证类型,Spring 环境下忽略 |
shareable | boolean | 是否共享,Spring 环境下忽略 |
mappedName | String | 映射名称,Spring 环境下忽略 |
description | String | 描述信息,Spring 环境下忽略 |
适用位置:字段(Field)、Setter 方法。不能用于构造器参数,这是与 @Autowired 的重要区别。
核心原理
Spring 的 CommonAnnotationBeanPostProcessor 负责处理 @Resource 注解。其解析流程与 @Autowired 有显著差异:
- 名称推导:如果显式指定了
name属性,直接使用;否则,对于字段注入,取字段名作为名称;对于 Setter 注入,取Setter 方法名去掉 "set" 前缀并首字母小写作为名称 - 按名称查找:在 Spring 容器的
BeanFactory中查找名称匹配的 Bean - 回退按类型:如果按名称找不到,则按类型查找。如果按类型找到多个,抛出
NoUniqueBeanDefinitionException
完整示例
场景简述
飞翔科技的学生管理系统需要同时对接 MySQL 主库和 Redis 缓存。李眉在旧模块中大量使用 @Resource 注入基础设施组件。小崔在维护时发现,理解 @Resource 的名称推导规则对于排查注入问题至关重要。
操作前:手动查找 Bean 的繁琐代码
public class StudentService {
private DataSource dataSource;
private RedisTemplate<String, String> redisTemplate;
public StudentService() {
// 手动从某个工厂或上下文查找,耦合严重
this.dataSource = LegacyFactory.getDataSource("masterDataSource");
this.redisTemplate = LegacyFactory.getRedisTemplate();
}
}
痛点分析:
- 依赖查找逻辑散落在业务代码中,无法统一管理
- 单元测试时必须初始化整个
LegacyFactory - 组件名称变更后,所有硬编码引用点都需要修改
使用该注解的完整代码
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 org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import javax.sql.DataSource;
@Configuration
public class InfrastructureConfig {
@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);
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}
package com.feixiang.student.service;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Service
public class StudentService {
// 按字段名 "masterDataSource" 查找容器中同名的 Bean
@Resource
private DataSource masterDataSource;
// 显式指定 name,注入从库数据源
@Resource(name = "slaveDataSource")
private DataSource reportDataSource;
public void printDataSourceInfo() {
System.out.println("业务数据源:" + masterDataSource.getClass().getSimpleName());
System.out.println("报表数据源:" + reportDataSource.getClass().getSimpleName());
}
}
package com.feixiang.student.service;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class CacheService {
private RedisTemplate<String, String> redisTemplate;
// Setter 注入:按 "setRedisTemplate" 推导名称为 "redisTemplate"
@Resource
public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void cacheStudentName(Long studentId, String name) {
redisTemplate.opsForValue().set("student:name:" + studentId, name);
}
}
package com.feixiang.student;
import com.feixiang.student.config.InfrastructureConfig;
import com.feixiang.student.service.CacheService;
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(InfrastructureConfig.class);
ctx.scan("com.feixiang.student.service");
ctx.refresh();
StudentService studentService = ctx.getBean(StudentService.class);
studentService.printDataSourceInfo();
CacheService cacheService = ctx.getBean(CacheService.class);
cacheService.cacheStudentName(1L, "小崔");
System.out.println("缓存写入成功");
ctx.close();
}
}
操作后运行结果及分析
业务数据源:HikariDataSource
报表数据源:HikariDataSource
缓存写入成功
变化分析:
StudentService中的masterDataSource字段名与@Bean方法名一致,@Resource按名称精确注入主库数据源reportDataSource通过@Resource(name = "slaveDataSource")显式指定,注入从库数据源,实现读写分离CacheService通过 Setter 注入,@Resource根据方法名setRedisTemplate推导出名称redisTemplate,与容器中的 Bean 名称匹配- 所有依赖由容器管理,业务代码无需关心对象如何创建
@Resource 与 @Autowired 的对比
| 维度 | @Resource | @Autowired |
|---|---|---|
| 规范来源 | JSR-250(Java 标准) | Spring 专有 |
| 默认装配策略 | 按名称(byName) | 按类型(byType) |
| 回退策略 | 名称找不到则按类型 | 类型有多个则按名称/Qualifier |
| 可用位置 | 字段、Setter 方法 | 字段、Setter、构造器 |
| required 控制 | 无显式属性,找不到即报错 | 有 required = false |
| 配合 Qualifier | 不支持(自身 name 属性即限定) | 需配合 @Qualifier |
| 构造器注入 | 不支持 | 支持(官方推荐) |
易错场景与面试考点
易错场景一:字段名与 Bean 名称不一致导致意外回退
@Bean
public DataSource masterDataSource() { ... }
@Service
public class StudentService {
@Resource
private DataSource dataSource; // 字段名是 dataSource,不是 masterDataSource
}
后果:按名称 dataSource 找不到 Bean,回退按类型 DataSource 查找,找到 masterDataSource 和 slaveDataSource 两个候选,抛出 NoUniqueBeanDefinitionException。
正确做法:保持字段名与 Bean 名称一致,或显式指定 @Resource(name = "masterDataSource")。
易错场景二:Setter 方法命名不规范导致注入失败
@Service
public class StudentService {
private DataSource masterDataSource;
// 错误的方法名:没有遵循 setXxx 规范
@Resource
public void injectMaster(DataSource ds) {
this.masterDataSource = ds;
}
}
后果:@Resource 推导出的名称是 injectMaster,容器中不存在该名称的 Bean,回退按类型查找又遇到多个 DataSource,最终报错。
正确做法:Setter 方法必须遵循 JavaBean 命名规范 setPropertyName,或显式指定 @Resource(name = "masterDataSource")。
易错场景三:试图在构造器上使用 @Resource
@Service
public class StudentService {
private final DataSource dataSource;
@Resource // 编译错误或不生效
public StudentService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
后果:JSR-250 规范未定义 @Resource 在构造器上的行为,Spring 会忽略构造器上的 @Resource,导致依赖未被注入(若存在默认无参构造器则使用该构造器,否则报错)。
正确做法:构造器注入必须使用 @Autowired(或 Spring 4.3+ 单构造器省略)。
面试考点
Q:@Resource 默认按名称装配,那它如何推导名称?
对于字段注入,名称就是字段名本身;对于 Setter 注入,名称是去掉 "set" 前缀并将首字母小写后的字符串。例如
setMasterDataSource(DataSource ds)推导出的名称是masterDataSource。如果显式指定了@Resource(name = "xxx"),则直接使用该值。
Q:@Resource 和 @Autowired 在只有一个同类型 Bean 时的行为是否相同?
行为结果相同(都能成功注入),但查找路径不同:
@Resource先按名称找,找不到再按类型找;@Autowired直接按类型找。如果字段名恰好与 Bean 名一致,@Resource在第一步就命中,效率略高。
Q:@Resource 能否解决同类型多 Bean 的歧义?
可以,但方式与
@Autowired不同。@Resource通过name属性精确指定 Bean 名称,本质上是"按名称直接定位",而非在类型匹配的候选集中筛选。如果name指定错误,即使存在同类型的其他 Bean 也不会回退注入(除非按名称找不到才回退按类型,但回退后若仍有多个候选仍会报错)。
Q:为什么 Spring 官方推荐 @Autowired 而非 @Resource?
①
@Autowired支持构造器注入,这是官方推荐的最佳实践;②@Autowired配合@Qualifier可以实现更灵活的限定策略(如自定义限定符注解);③@Autowired的required = false对可选依赖的支持更明确。@Resource的优势在于它是 Java 标准注解,代码不绑定 Spring API,适合需要跨容器移植的场景。