@Autowired和@Resource的区别及动态选择 Bean 的方法
在 Spring Boot 中,
@Autowired和@Resource都是用于依赖注入的注解,但它们有着不同的来源、功能和使用方式。理解它们的区别以及如何在存在多个相同类型 Bean 的情况下动态选择注入,是 Spring 开发中的常见问题。下面我会详细解答这两个问题。
1. @Autowired 和 @Resource 的区别
1.1 来源和规范
@Autowired:- 来源:Spring 框架提供的注解,属于 Spring 的核心注解。
- 规范:基于 Spring 的依赖注入机制,遵循 Spring 的 IoC 容器规则。
@Resource:- 来源:Java 标准注解,来自 JSR-250 规范(Java EE 的一部分)。
- 规范:属于 Java EE 规范,Spring 实现了对它的支持,但它的行为更接近 Java EE 的 CDI(Context and Dependency Injection)。
1.2 注入方式
@Autowired:- 默认按 类型(byType)进行注入。
- 如果容器中存在多个相同类型的 Bean,会尝试按 名称(byName)匹配(结合
@Qualifier或字段名)。 - 支持构造函数、字段、Setter 方法注入。
@Resource:- 默认按 名称(byName)进行注入。
- 如果未指定名称,则按类型(byType)注入。
- 仅支持字段和 Setter 方法注入,不支持构造函数注入。
1.3 匹配规则
-
@Autowired:-
优先按类型查找 Bean。
-
如果找到多个相同类型的 Bean,会抛出
NoUniqueBeanDefinitionException,除非:- 使用
@Qualifier指定 Bean 名称。 - 字段名或参数名与某个 Bean 的名称匹配(Spring 4.3+ 支持)。
- 使用
-
示例:
@Autowired private MyService myService; // 按类型查找 MyService
-
-
@Resource:-
优先按名称查找 Bean(默认使用字段名或
name属性)。 -
如果按名称未找到,则按类型查找。
-
如果按类型找到多个 Bean,会抛出异常。
-
示例:
@Resource(name = "myServiceImpl") private MyService myService; // 按名称 "myServiceImpl" 查找
-
1.4 依赖查找失败时的行为
-
@Autowired:-
默认情况下,如果找不到匹配的 Bean,会抛出
NoSuchBeanDefinitionException。 -
可以通过设置
required = false使注入可选:@Autowired(required = false) private MyService myService; // 如果找不到 Bean,myService 为 null
-
-
@Resource:- 如果找不到匹配的 Bean,会抛出异常。
- 没有类似
required的属性,无法直接设置注入为可选。
1.5 适用场景
@Autowired:- 更适合 Spring 生态,灵活性更高,支持构造函数注入。
- 常用于需要按类型注入或结合
@Qualifier动态选择的场景。
@Resource:- 更适合 Java EE 环境,或者需要按名称注入的场景。
- 如果项目中需要跨框架兼容(例如可能迁移到其他支持 JSR-250 的框架),使用
@Resource更通用。
1.6 优先级和注解组合
@Autowired:- 可以与
@Qualifier组合,指定注入的 Bean 名称。 - 支持
@Primary注解,指定默认优先级。
- 可以与
@Resource:- 不支持
@Qualifier,但可以通过name属性指定 Bean。 - 不直接支持
@Primary,但 Spring 在处理时会考虑@Primary的优先级。
- 不支持
1.7 总结表
| 特性 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring 框架 | JSR-250(Java EE) |
| 注入方式 | 优先按类型(byType),后按名称(byName) | 优先按名称(byName),后按类型(byType) |
| 支持的注入方式 | 字段、Setter、构造函数 | 字段、Setter |
| 指定 Bean | 使用 @Qualifier | 使用 name 属性 |
| 可选注入 | 支持 required = false | 不支持 |
| 适用场景 | Spring 生态,灵活性高 | Java EE 兼容,按名称注入 |
2. 存在多个相同类型 Bean 时如何动态选择
在 Spring 容器中,如果存在多个相同类型的 Bean(例如多个 MyService 实现类),Spring 默认无法确定注入哪个 Bean,会抛出 NoUniqueBeanDefinitionException。以下是几种动态选择 Bean 的方法,适用于 @Autowired 和 @Resource。
2.1 使用 @Qualifier 指定 Bean 名称(@Autowired)
@Qualifier 注解可以与 @Autowired 一起使用,指定注入的 Bean 名称。
示例:
-
定义接口和实现类:
public interface MyService { String doSomething(); } @Component("serviceA") public class ServiceA implements MyService { @Override public String doSomething() { return "Service A"; } } @Component("serviceB") public class ServiceB implements MyService { @Override public String doSomething() { return "Service B"; } } -
使用
@Qualifier注入:@Component public class MyController { private final MyService myService; @Autowired public MyController(@Qualifier("serviceA") MyService myService) { this.myService = myService; } public String execute() { return myService.doSomething(); } }@Qualifier("serviceA")指定注入serviceA对应的 Bean。
2.2 使用 @Resource 指定名称
@Resource 本身支持通过 name 属性指定 Bean 名称。
示例:
@Component
public class MyController {
@Resource(name = "serviceB")
private MyService myService;
public String execute() {
return myService.doSomething();
}
}
name = "serviceB"指定注入serviceB对应的 Bean。
2.3 使用 @Primary 指定默认 Bean
如果某个 Bean 是默认首选,可以使用 @Primary 注解。
示例:
@Component
@Primary
public class ServiceA implements MyService {
@Override
public String doSomething() {
return "Service A";
}
}
@Component
public class ServiceB implements MyService {
@Override
public String doSomething() {
return "Service B";
}
}
注入时:
@Component
public class MyController {
@Autowired
private MyService myService; // 自动注入 ServiceA,因为它有 @Primary
public String execute() {
return myService.doSomething();
}
}
@Primary优先级高于字段名匹配,但会被@Qualifier覆盖。
2.4 按字段名或参数名匹配(@Autowired)
如果不使用 @Qualifier,Spring 会尝试按字段名或参数名匹配 Bean 名称(Spring 4.3+)。
示例:
@Component
public class MyController {
@Autowired
private MyService serviceA; // 字段名 "serviceA" 与 Bean 名称匹配
public String execute() {
return serviceA.doSomething();
}
}
- 字段名
serviceA与ServiceA的 Bean 名称匹配,自动注入。
2.5 注入所有 Bean 并动态选择
如果需要动态选择,可以注入所有相同类型的 Bean(使用 List 或 Map),然后根据条件选择。
示例:
-
注入所有
MyService实现:@Component public class MyController { private final Map<String, MyService> serviceMap; @Autowired public MyController(Map<String, MyService> serviceMap) { this.serviceMap = serviceMap; } public String execute(String serviceName) { MyService service = serviceMap.getOrDefault(serviceName, serviceMap.get("serviceA")); return service.doSomething(); } } -
调用:
@RestController public class ApiController { private final MyController myController; @Autowired public ApiController(MyController myController) { this.myController = myController; } @GetMapping("/execute") public String execute(@RequestParam String serviceName) { return myController.execute(serviceName); } }- 访问
/execute?serviceName=serviceB,会调用ServiceB的方法。 serviceMap的 key 是 Bean 名称,value 是对应的 Bean 实例。
- 访问
2.6 使用 @Conditional 动态创建 Bean
如果 Bean 的创建需要根据条件动态决定,可以使用 @Conditional 注解。
示例:
@Component
@ConditionalOnProperty(name = "service.type", havingValue = "A")
public class ServiceA implements MyService {
@Override
public String doSomething() {
return "Service A";
}
}
@Component
@ConditionalOnProperty(name = "service.type", havingValue = "B")
public class ServiceB implements MyService {
@Override
public String doSomething() {
return "Service B";
}
}
配置:
service.type=A
- 根据
service.type的值,Spring 只会创建对应的 Bean,避免多个 Bean 的冲突。
2.7 使用 FactoryBean 动态创建
可以通过 FactoryBean 动态创建 Bean。
示例:
@Component
public class MyServiceFactory implements FactoryBean<MyService> {
@Override
public MyService getObject() throws Exception {
String type = System.getProperty("service.type", "A");
if ("B".equals(type)) {
return new ServiceB();
}
return new ServiceA();
}
@Override
public Class<?> getObjectType() {
return MyService.class;
}
}
- 根据运行时条件(例如系统属性)动态创建
MyService实例。
3. 实际案例
场景
一个支付系统支持多种支付方式(支付宝、微信),需要根据用户选择的支付方式动态注入对应的支付服务。
代码实现
-
定义接口和实现:
public interface PaymentService { String pay(); } @Component("alipay") public class AlipayService implements PaymentService { @Override public String pay() { return "Pay with Alipay"; } } @Component("wechat") public class WechatService implements PaymentService { @Override public String pay() { return "Pay with Wechat"; } } -
动态选择注入:
@RestController public class PaymentController { private final Map<String, PaymentService> paymentServices; @Autowired public PaymentController(Map<String, PaymentService> paymentServices) { this.paymentServices = paymentServices; } @GetMapping("/pay") public String pay(@RequestParam String type) { PaymentService service = paymentServices.getOrDefault(type, paymentServices.get("alipay")); return service.pay(); } } -
测试:
- 访问
/pay?type=wechat,返回"Pay with Wechat"。 - 访问
/pay?type=alipay,返回"Pay with Alipay"。
- 访问
4. 总结
@Autowired 和 @Resource 的区别
@Autowired是 Spring 提供的,按类型注入,支持更多注入方式,灵活性高。@Resource是 Java EE 规范,按名称注入,跨框架兼容性好,但不支持构造函数注入。
动态选择 Bean 的方法
- 使用
@Qualifier或@Resource(name)指定 Bean 名称。 - 使用
@Primary指定默认 Bean。 - 按字段名或参数名匹配。
- 注入所有 Bean(
List或Map),运行时动态选择。 - 使用
@Conditional或FactoryBean动态创建 Bean。
推荐实践
- 优先使用
@Autowired,因为它是 Spring 生态的原生注解,功能更强大。 - 如果需要按名称注入,
@Resource是一个简洁的选择。 - 在存在多个 Bean 时,推荐使用
Map注入并动态选择,或者通过配置和条件注解控制 Bean 的创建。
微信
支付宝