什么是Java中的依赖注入,为什么需要它?
想象一下你在盖房子。你需要砖块、水泥和工具。如果你每次都自己去挖粘土、烧砖、煮水泥,那么建造过程将耗费无尽的时间。更简单的方式是,材料已经准备好并运送到工地。这正是依赖注入(DI)在Java中的工作方式——它是一种设计模式,对象从外部获取现成的依赖,而不是自己创建它们。
简单来说:依赖注入就是一个对象将所需的所有依赖传递给另一个对象。类不再自己创建依赖,而是通过构造函数、setter方法或接口来获取它们。
为什么依赖注入如此重要?
- 降低代码耦合度:类不再紧密依赖于具体的实现。
- 简化测试:在单元测试中,你可以轻松地将真实对象替换为模拟对象(mock对象)。
- 提高可复用性:同一个类可以在不同的上下文中使用。
- 改善可读性:代码变得更清晰,依赖关系也变得明确。
没有依赖注入的问题:反模式“紧耦合”
让我们看一个典型的没有使用DI的代码示例。假设我们有一个发送邮件的EmailService类,以及一个使用EmailService的NotificationService类。
// 没有DI的类——糟糕的例子public class EmailService { public void sendEmail(String message) { System.out.println("发送邮件: " + message); }}
public class NotificationService { private EmailService emailService;
public NotificationService() { // 问题:在类内部直接创建依赖 this.emailService = new EmailService(); }
public void sendNotification(String message) { emailService.sendEmail(message); }}这段代码的问题在哪里?
- 紧耦合:
NotificationService与EmailService紧密绑定。如果你想发送短信而不是邮件,就必须重写类。 - 测试困难:你无法将
EmailService替换为测试桩(mock)。 - 违反单一职责原则:
NotificationService类不仅负责通知,还负责创建发送服务。
依赖注入如何工作:三种注入方式
在Java中,有三种主要方式来实现依赖注入。让我们通过代码示例逐一介绍。
1. 构造函数注入
这是最常见且推荐的方式。依赖通过构造函数参数传递。
// 接口以实现灵活性public interface MessageService { void sendMessage(String message);}
// 邮件实现public class EmailService implements MessageService { @Override public void sendMessage(String message) { System.out.println("邮件已发送: " + message); }}
// 短信实现public class SmsService implements MessageService { @Override public void sendMessage(String message) { System.out.println("短信已发送: " + message); }}
// 通过构造函数实现DI的类public class NotificationService { private final MessageService messageService;
// 通过构造函数注入依赖 public NotificationService(MessageService messageService) { this.messageService = messageSe