Java-Spring 依赖注入详解--多个类实现与选择

多个接口实现的解决方案 - 实战示例

🤔 问题场景

假设你有一个 NotificationService 接口,有两个实现类:

// 接口
public interface NotificationService {
    void send(String message);
}

// 实现1:发邮件
@Component
public class EmailNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("发送邮件: " + message);
    }
}

// 实现2:发短信
@Component
public class SmsNotificationService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("发送短信: " + message);
    }
}

现在你想在某个类中使用 NotificationService

@Service
public class OrderService {
    @Autowired
    private NotificationService notificationService;  // ❌ 错误!Spring 不知道用哪个
}

Spring 会报错:

NoUniqueBeanDefinitionException: No qualifying bean of type 'NotificationService' available: 
expected single matching bean but found 2: emailNotificationService, smsNotificationService

✅ 解决方案

方案1:使用 @Qualifier(明确指定)

使用场景: 你需要明确知道用哪个实现

// 实现类(可以指定 Bean 名称)
@Component("emailService")  // 自定义名称,不写的话默认是 emailNotificationService
public class EmailNotificationService implements NotificationService {
    // ...
}

@Component("smsService")
public class SmsNotificationService implements NotificationService {
    // ...
}

// 使用时指定
@Service
public class OrderService {
    @Autowired
    @Qualifier("emailService")  // 👈 明确指定用 emailService
    private NotificationService notificationService;
    
    public void createOrder() {
        notificationService.send("订单创建成功");  // 会调用 EmailNotificationService
    }
}

FastBee 项目中的实际例子:

// SipCmdImpl.java
@Autowired
@Qualifier(value = "udpSipServer")  // 明确指定要用 "udpSipServer" 这个 Bean
private SipProvider sipserver;

方案2:使用 @Primary(设置默认)

使用场景: 有一个是默认实现,大部分情况都用它

@Component
@Primary  // 👈 标记为默认实现
public class EmailNotificationService implements NotificationService {
    // ...
}

@Component
public class SmsNotificationService implements NotificationService {
    // ...
}

// 使用时不需要指定,自动用 @Primary 标记的
@Service
public class OrderService {
    @Autowired
    private NotificationService notificationService;  // ✅ 会自动用 EmailNotificationService
    
    public void createOrder() {
        notificationService.send("订单创建成功");
    }
}

// 如果某个地方需要明确用 SMS,可以配合 @Qualifier
@Service
public class AlertService {
    @Autowired
    @Qualifier("smsNotificationService")  // 明确用 SMS
    private NotificationService notificationService;
}

FastBee 项目中的实际例子:

// DruidConfig.java
@Bean(name = "dynamicDataSource")
@Primary  // 标记为主要数据源,默认都用这个
public DynamicDataSource dataSource(DataSource masterDataSource) {
    // ...
}

方案3:注入所有实现(List/Map)

使用场景: 你需要使用所有的实现,比如广播消息

使用 List

@Service
public class BroadcastService {
    @Autowired
    private List<NotificationService> notificationServices;  // 👈 注入所有实现
    
    public void broadcast(String message) {
        // 遍历所有实现,都发送一遍
        for (NotificationService service : notificationServices) {
            service.send(message);
        }
        // 结果:
        // 发送邮件: 消息内容
        // 发送短信: 消息内容
    }
}

使用 Map(可以按名称获取)

@Service
public class NotificationManager {
    @Autowired
    private Map<String, NotificationService> notificationServiceMap;  
    // Map 包含:
    // "emailNotificationService" -> EmailNotificationService 实例
    // "smsNotificationService" -> SmsNotificationService 实例
    
    public void sendByType(String type, String message) {
        NotificationService service = notificationServiceMap.get(type);
        if (service != null) {
            service.send(message);
        }
    }
    
    // 使用
    // sendByType("emailNotificationService", "消息");  // 发邮件
    // sendByType("smsNotificationService", "消息");    // 发短信
}

方案4:使用条件注解(根据配置选择)

使用场景: 根据配置文件决定用哪个实现

// 生产环境用邮件
@Component
@ConditionalOnProperty(name = "notification.type", havingValue = "email")
public class EmailNotificationService implements NotificationService {
    // ...
}

// 测试环境用短信
@Component
@ConditionalOnProperty(name = "notification.type", havingValue = "sms")
public class SmsNotificationService implements NotificationService {
    // ...
}

配置文件 application.yml:

notification:
  type: email  # 只有 EmailNotificationService 会被创建

📊 方案对比表

方案 什么时候用 优点 缺点
@Qualifier 需要明确指定用哪个 最灵活,清晰明确 需要记住 Bean 名称
@Primary 有一个默认实现 简单,不需要指定 不够灵活,可能混淆
List/Map 需要所有实现 可以统一处理 不适合只用一个的情况
条件注解 根据配置选择 灵活切换环境 配置较复杂

💡 推荐使用建议

场景1:开发和生产用不同的实现

// 推荐:使用 @Primary + @Qualifier 组合
@Component
@Primary
public class EmailNotificationService implements NotificationService { }

@Component
@ConditionalOnProperty(name = "env", havingValue = "test")
public class MockNotificationService implements NotificationService { }

场景2:大部分地方用默认,少数地方用特殊的

// 推荐:使用 @Primary
@Component
@Primary  // 默认用这个
public class EmailNotificationService implements NotificationService { }

@Component
public class SmsNotificationService implements NotificationService { }

// 默认用 Email
@Autowired
private NotificationService service;  

// 特殊地方用 SMS
@Autowired
@Qualifier("smsNotificationService")
private NotificationService smsService;

场景3:需要发送到多个渠道

// 推荐:使用 List
@Autowired
private List<NotificationService> services;

public void notifyAll(String message) {
    services.forEach(service -> service.send(message));
}

🎯 记忆口诀

一个接口多个实现,Spring 不知道用哪个

  • 要明确指定 → 用 @Qualifier
  • 有默认首选 → 用 @Primary
  • 全都要用 → 用 ListMap
  • 按配置选 → 用条件注解

🔍 FastBee 项目中的真实案例

案例1:使用 @Qualifier 指定 Bean

// SipCmdImpl.java
@Autowired
@Qualifier(value = "udpSipServer")  // 明确指定要用名为 "udpSipServer" 的 Bean
private SipProvider sipserver;

案例2:使用 @Primary 设置默认

// DruidConfig.java
@Bean(name = "dynamicDataSource")
@Primary  // 标记为主要数据源
public DynamicDataSource dataSource(DataSource masterDataSource) {
    // ...
}

案例3:多个实现类通过 Map 管理

在 FastBee 项目中,多个 IReqHandler 实现类(如 RegisterReqHandlerInviteReqHandler 等)通过手动注册到 Map 中管理:

// GBListenerImpl.java
private static final Map<String, IReqHandler> requestProcessorMap = new ConcurrentHashMap<>();

public void addRequestProcessor(String method, IReqHandler processor) {
    requestProcessorMap.put(method, processor);  // 根据 method 选择不同的处理器
}

希望这个例子能帮你理解! 🚀

posted @ 2025-12-25 23:19  若-飞  阅读(1)  评论(0)    收藏  举报