Java 依赖注入 - DI 设计模式示例教程
Java 依赖注入设计模式允许我们删除硬编码的依赖项,并使我们的应用程序松散耦合、可扩展和可维护。我们可以在 Java 中实现依赖注入,将依赖项解析从编译时移至运行时。
Java 依赖注入
Java 依赖注入似乎很难从理论上理解,所以我将举一个简单的例子,然后我们将看到如何使用依赖注入模式在应用程序中实现松散耦合和可扩展性。假设我们有一个应用程序,我们用它来EmailService
发送电子邮件。通常我们会像下面这样实现它。
package com.journaldev.java.legacy;
public class EmailService {
public void sendEmail(String message, String receiver){
//logic to send email
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
EmailService
类包含向收件人电子邮件地址发送电子邮件消息的逻辑。我们的应用程序代码如下所示。
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = new EmailService();
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.email.sendEmail(msg, rec);
}
}
我们的使用类发送电子邮件消息的客户端代码MyApplication
如下所示。
package com.journaldev.java.legacy;
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "pankaj@abc.com");
}
}
乍一看,上述实现似乎没有什么问题。但上述代码逻辑存在一定的局限性。
MyApplication
类负责初始化电子邮件服务,然后使用它。这会导致硬编码依赖关系。如果我们将来想切换到其他高级电子邮件服务,则需要更改 MyApplication 类中的代码。这使得我们的应用程序很难扩展,如果电子邮件服务在多个类中使用,那么这将更加困难。- 如果我们想扩展我们的应用程序以提供额外的消息传递功能,例如 SMS 或 Facebook 消息,那么我们需要为此编写另一个应用程序。这也将涉及应用程序类和客户端类中的代码更改。
- 测试应用程序将非常困难,因为我们的应用程序直接创建了电子邮件服务实例。我们无法在测试类中模拟这些对象。
MyApplication
有人可能会说,我们可以通过使用需要电子邮件服务作为参数的构造函数来从类中删除电子邮件服务实例的创建。
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = null;
public MyApplication(EmailService svc){
this.email=svc;
}
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.email.sendEmail(msg, rec);
}
}
但在这种情况下,我们要求客户端应用程序或测试类初始化电子邮件服务,这不是一个好的设计决策。现在让我们看看如何应用 Java 依赖注入模式来解决上述实现的所有问题。Java 中的依赖注入至少需要以下内容:
- 服务组件应该用基类或接口来设计。最好使用接口或抽象类来定义服务的契约。
- 应该根据服务接口来编写消费者类。
- 注入器类将初始化服务,然后初始化消费者类。
Java 依赖注入 - 服务组件
对于我们的情况,我们可以MessageService
声明服务实现的合同。
package com.journaldev.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
现在假设我们有实现上述接口的电子邮件和短信服务。
package com.journaldev.java.dependencyinjection.service;
public class EmailServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logic to send email
System.out.println("Email sent to "+rec+ " with Message="+msg);
}
}
package com.journaldev.java.dependencyinjection.service;
public class SMSServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logic to send SMS
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
我们的依赖注入 Java 服务已经准备就绪,现在我们可以编写我们的消费者类。
Java 依赖注入 - 服务消费者
我们不需要为消费者类提供基接口,但我会Consumer
为消费者类提供一个接口声明契约。
package com.journaldev.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
我的消费者类实现如下所示。
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(MessageService svc){
this.service=svc;
}
@Override
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.service.sendMessage(msg, rec);
}
}
请注意,我们的应用程序类只是使用该服务。它不会初始化服务,从而实现更好的“关注点分离”。此外,使用服务接口允许我们通过模拟 MessageService 并在运行时(而不是编译时)绑定服务来轻松测试应用程序。现在我们准备编写Java 依赖注入器类来初始化服务和消费者类。
Java 依赖注入 - 注入器类
让我们有一个MessageServiceInjector
带有返回类的方法声明的接口Consumer
。
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
现在,对于每项服务,我们都必须创建如下所示的注入器类。
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
现在让我们看看我们的客户端应用程序如何通过一个简单的程序使用该应用程序。
package com.journaldev.java.dependencyinjection.test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) {
String msg = "Hi Pankaj";
String email = "pankaj@abc.com";
String phone = "4088888888";
MessageServiceInjector injector = null;
Consumer app = null;
//Send email
injector = new EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//Send SMS
injector = new SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
如您所见,我们的应用程序类仅负责使用服务。服务类是在注入器中创建的。此外,如果我们必须进一步扩展我们的应用程序以允许 Facebook 消息传递,我们将不得不仅编写服务类和注入器类。因此,依赖注入实现解决了硬编码依赖的问题,并帮助我们使我们的应用程序灵活且易于扩展。现在让我们看看如何通过模拟注入器和服务类来轻松测试我们的应用程序类。
Java 依赖注入 - 带有模拟注入器和服务的 JUnit 测试用例
package com.journaldev.java.dependencyinjection.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplicationJUnitTest {
private MessageServiceInjector injector;
@Before
public void setUp(){
//mock the injector with anonymous class
injector = new MessageServiceInjector() {
@Override
public Consumer getConsumer() {
//mock the message service
return new MyDIApplication(new MessageService() {
@Override
public void sendMessage(String msg, String rec) {
System.out.println("Mock Message Service implementation");
}
});
}
};
}
@Test
public void test() {
Consumer consumer = injector.getConsumer();
consumer.processMessages("Hi Pankaj", "pankaj@abc.com");
}
@After
public void tear(){
injector = null;
}
}
如您所见,我使用匿名类来模拟注入器和服务类,并且我可以轻松测试我的应用程序方法。我对上述测试类使用了 JUnit 4,因此如果您正在运行上述测试类,请确保它在您的项目构建路径中。我们已经使用构造函数在应用程序类中注入依赖项,另一种方法是使用 setter 方法在应用程序类中注入依赖项。对于 setter 方法依赖注入,我们的应用程序类将按如下所示实现。
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(){}
//setter dependency injection
public void setService(MessageService service) {
this.service = service;
}
@Override
public void processMessages(String msg, String rec){
//do some msg validation, manipulation logic etc
this.service.sendMessage(msg, rec);
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
设置器依赖注入的最佳示例之一是Struts2 Servlet API Aware 接口。是否使用基于构造函数的依赖注入或基于 setter 是一个设计决策,取决于您的要求。例如,如果我的应用程序没有服务类就根本无法工作,那么我更喜欢基于构造函数的 DI,否则我会选择基于 setter 方法的 DI,以便只在真正需要时使用它。Java中的依赖注入是一种通过将对象绑定从编译时移动到运行时来实现应用程序中的控制反转(IoC )的方法。我们也可以通过工厂模式、模板方法设计模式、策略模式和服务定位器模式实现 IoC。Spring Dependency Injection、Google Guice和Java EE CDI框架通过使用Java Reflection API和java 注释促进了依赖注入的过程。我们所需要做的就是注释字段、构造函数或 setter 方法,并在配置 xml 文件或类中配置它们。
Java 依赖注入的好处
在 Java 中使用依赖注入的一些好处包括:
- 关注点分离
- 应用程序类中的样板代码减少,因为初始化依赖项的所有工作都由注入器组件处理
- 可配置组件使应用程序易于扩展
- 使用模拟对象可以轻松进行单元测试
Java 依赖注入的缺点
Java依赖注入也有一些缺点:
- 如果过度使用,可能会导致维护问题,因为更改的效果在运行时是已知的。
- Java 中的依赖注入隐藏了服务类依赖关系,这些依赖关系可能会导致在编译时捕获的运行时错误。
下载依赖注入项目
这就是Java 中的依赖注入模式的全部内容。当我们控制服务时,了解和使用它很有用。