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
class保存将电子邮件消息发送到收件人电子邮件地址的逻辑。我们的应用程序代码如下所示。
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
class负责初始化电子邮件服务,然后使用它。这导致了硬编码的依赖性。如果我们希望将来切换到其他一些高级电子邮件服务,则需要在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);
}
现在让我们说我们有实现上述接口的电子邮件和SMS服务。
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;
}
}
setter依赖注入的最好例子之一是Struts2 Servlet API Aware接口。
是否使用基于构造函数的依赖注入或基于setter是一个设计决策,取决于您的要求。例如,如果我的应用程序在没有服务类的情况下根本无法工作,那么我会更喜欢基于构造函数的DI,否则我会选择基于setter方法的DI,只有在真正需要时才使用它。
Java中的依赖注入是一种通过将对象从编译时绑定到运行时来实现应用程序中的控制反转(IoC)的方法。我们也可以通过工厂模式,模板方法设计模式,策略模式和服务定位模式来实现IoC 。
Spring依赖注入, Google Guice和 Java EE CDI框架通过使用 Java Reflection API和 Java注释来促进依赖注入的过程。我们所需要的只是注释字段,构造函数或setter方法,并在配置xml文件或类中配置它们。
Java依赖注入的好处
在Java中使用依赖注入的一些好处是:
- 关注点分离
- Boilerplate应用程序类中的代码减少,因为初始化依赖项的所有工作都由注入器组件处理
- 可配置组件使应用程序易于扩展
- 使用模拟对象可以轻松进行单元测试
Java依赖注入的缺点
Java依赖注入也有一些缺点:
- 如果过度使用,可能会导致维护问题,因为更改的影响在运行时已知。
- java中的依赖注入隐藏了可能导致在编译时捕获的运行时错误的服务类依赖项。
这就是java中依赖注入模式的全部内容。当我们控制服务时,了解并使用它是很好的。
转载来源:https://www.journaldev.com/2394/java-dependency-injection-design-pattern-example-tutorial