在 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 名称。

示例

  1. 定义接口和实现类:

    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";
        }
    }
    
  2. 使用 @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();
    }
}
  • 字段名 serviceAServiceA 的 Bean 名称匹配,自动注入。

2.5 注入所有 Bean 并动态选择

如果需要动态选择,可以注入所有相同类型的 Bean(使用 ListMap),然后根据条件选择。

示例

  1. 注入所有 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();
        }
    }
    
  2. 调用:

    @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. 实际案例

场景

一个支付系统支持多种支付方式(支付宝、微信),需要根据用户选择的支付方式动态注入对应的支付服务。

代码实现

  1. 定义接口和实现:

    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";
        }
    }
    
  2. 动态选择注入:

    @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();
        }
    }
    
  3. 测试:

    • 访问 /pay?type=wechat,返回 "Pay with Wechat"
    • 访问 /pay?type=alipay,返回 "Pay with Alipay"

4. 总结

@Autowired 和 @Resource 的区别

  • @Autowired 是 Spring 提供的,按类型注入,支持更多注入方式,灵活性高。
  • @Resource 是 Java EE 规范,按名称注入,跨框架兼容性好,但不支持构造函数注入。

动态选择 Bean 的方法

  1. 使用 @Qualifier@Resource(name) 指定 Bean 名称。
  2. 使用 @Primary 指定默认 Bean。
  3. 按字段名或参数名匹配。
  4. 注入所有 Bean(ListMap),运行时动态选择。
  5. 使用 @ConditionalFactoryBean 动态创建 Bean。

推荐实践

  • 优先使用 @Autowired,因为它是 Spring 生态的原生注解,功能更强大。
  • 如果需要按名称注入,@Resource 是一个简洁的选择。
  • 在存在多个 Bean 时,推荐使用 Map 注入并动态选择,或者通过配置和条件注解控制 Bean 的创建。
文章作者: LibSept24_
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LibSept24_
SpingBoot
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝