Java依赖注入 – DI设计模式示例教程

Java的依赖注入设计模式允许我们消除硬编码的依赖,并使我们的应用程序实现松耦合、可扩展和易维护。我们可以在Java中实现依赖注入,将依赖解析从编译时移到运行时。

Java依赖注入

Java的依赖注入在理论上似乎很难理解,所以我会举一个简单的例子,然后我们将看到如何使用依赖注入模式来实现应用程序的松耦合和可扩展性。假设我们有一个应用程序,我们使用EmailService来发送电子邮件。通常情况下,我们会像下面这样实现。

package com.Olivia.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.Olivia.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.Olivia.java.legacy;

public class MyLegacyTest {

	public static void main(String[] args) {
		MyApplication app = new MyApplication();
		app.processMessages("Hi Pankaj", "scdev@abc.com");
	}

}

乍一看,上述实现似乎没有问题。但是,上述代码逻辑存在一定的限制。

  • MyApplication class is responsible to initialize the email service and then use it. This leads to hard-coded dependency. If we want to switch to some other advanced email service in the future, it will require code changes in MyApplication class. This makes our application hard to extend and if email service is used in multiple classes then that would be even harder.
  • If we want to extend our application to provide an additional messaging feature, such as SMS or Facebook message then we would need to write another application for that. This will involve code changes in application classes and in client classes too.
  • Testing the application will be very difficult since our application is directly creating the email service instance. There is no way we can mock these objects in our test classes.

可以说,我们可以通过在MyApplication类中添加一个要求电子邮件服务作为参数的构造函数,来取消电子邮件服务实例的创建。

package com.Olivia.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的依赖注入至少需要以下内容:

    1. 服务组件应该设计为基类或接口。最好选择接口或抽象类来定义服务的契约。

 

    1. 消费者类应该按照服务接口编写。

 

    注入器类负责初始化服务,然后再初始化消费者类。

Java 依赖注入 – 服务组件

对于我们的情况,我们可以有一个 MessageService 用来声明服务的实现合约。

package com.Olivia.java.dependencyinjection.service;

public interface MessageService {

	void sendMessage(String msg, String rec);
}

现在假设我们有实现上述接口的电子邮件和短信服务。

package com.Olivia.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.Olivia.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依赖注入 – 服务消费者

我们不需要消费者类的基接口,但是我会有一个声明消费者类合同的消费者接口。

package com.Olivia.java.dependencyinjection.consumer;

public interface Consumer {

	void processMessages(String msg, String rec);
}

我的消费者类实现如下。

package com.Olivia.java.dependencyinjection.consumer;

import com.Olivia.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);
	}

}

请注意,我们的应用程序类仅仅使用服务,而不是初始化服务,这样可以更好地实现“关注点分离”。同时,使用服务接口使我们能够通过模拟消息服务来轻松测试应用程序,并在运行时而不是编译时绑定服务。现在我们可以写Java依赖注入器类,这些类会初始化服务和消费类。

Java 依赖注入 – 注入器类

让我们创建一个接口MessageServiceInjector,其中有一个方法声明,返回Consumer类。

package com.Olivia.java.dependencyinjection.injector;

import com.Olivia.java.dependencyinjection.consumer.Consumer;

public interface MessageServiceInjector {

	public Consumer getConsumer();
}

现在对于每项服务,我们都需要像下面这样创建注入器类。

package com.Olivia.java.dependencyinjection.injector;

import com.Olivia.java.dependencyinjection.consumer.Consumer;
import com.Olivia.java.dependencyinjection.consumer.MyDIApplication;
import com.Olivia.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new EmailServiceImpl());
	}

}
package com.Olivia.java.dependencyinjection.injector;

import com.Olivia.java.dependencyinjection.consumer.Consumer;
import com.Olivia.java.dependencyinjection.consumer.MyDIApplication;
import com.Olivia.java.dependencyinjection.service.SMSServiceImpl;

public class SMSServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		return new MyDIApplication(new SMSServiceImpl());
	}

}

现在让我们看看我们的客户应用程序将如何使用这个应用程序与一个简单的程序。

package com.Olivia.java.dependencyinjection.test;

import com.Olivia.java.dependencyinjection.consumer.Consumer;
import com.Olivia.java.dependencyinjection.injector.EmailServiceInjector;
import com.Olivia.java.dependencyinjection.injector.MessageServiceInjector;
import com.Olivia.java.dependencyinjection.injector.SMSServiceInjector;

public class MyMessageDITest {

	public static void main(String[] args) {
		String msg = "Hi Pankaj";
		String email = "scdev@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测试用例,使用Mock注入器和服务

package com.Olivia.java.dependencyinjection.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.Olivia.java.dependencyinjection.consumer.Consumer;
import com.Olivia.java.dependencyinjection.consumer.MyDIApplication;
import com.Olivia.java.dependencyinjection.injector.MessageServiceInjector;
import com.Olivia.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", "scdev@abc.com");
	}
	
	@After
	public void tear(){
		injector = null;
	}

}

如您所见,我正在使用匿名类来模拟注入器和服务类,以便轻松测试我的应用程序方法。我正在使用JUnit 4进行上述测试类,因此,如果您正在运行上述测试类,请确保该类在项目的构建路径中。我们使用构造函数来在应用程序类中注入依赖项,另一种方式是使用setter方法在应用程序类中注入依赖项。对于setter方法依赖注入,我们的应用程序类将被实现如下。

package com.Olivia.java.dependencyinjection.consumer;

import com.Olivia.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.Olivia.java.dependencyinjection.injector;

import com.Olivia.java.dependencyinjection.consumer.Consumer;
import com.Olivia.java.dependencyinjection.consumer.MyDIApplication;
import com.Olivia.java.dependencyinjection.service.EmailServiceImpl;

public class EmailServiceInjector implements MessageServiceInjector {

	@Override
	public Consumer getConsumer() {
		MyDIApplication app = new MyDIApplication();
		app.setService(new EmailServiceImpl());
		return app;
	}

}

依赖注入是实现控制反转(IoC)的一种方式,通过将对象绑定从编译时移动到运行时来实现。在Java中,我们可以通过工厂模式、模板方法设计模式、策略模式和服务定位器模式来实现IoC。Spring依赖注入、Google Guice和Java EE CDI框架通过使用Java Reflection API和Java注解来简化依赖注入的过程。我们只需在配置XML文件或类中注释字段、构造函数或setter方法即可。

其中一个最好的setter依赖注入的例子是Struts2的Servlet API Aware接口。使用构造函数基于依赖注入还是基于setter的依赖注入是一个设计决策,取决于您的需求。例如,如果我的应用程序没有服务类根本无法工作,我会更倾向于选择基于构造函数的DI。否则,当确实需要时,我会选择基于setter方法的DI来使用它。

Java依赖注入的好处

在Java中使用依赖注入的一些好处是:

  • Separation of Concerns
  • Boilerplate Code reduction in application classes because all work to initialize dependencies is handled by the injector component
  • Configurable components makes application easily extendable
  • Unit testing is easy with mock objects

Java 依赖注入的缺点

Java的依赖注入也有一些缺点:

  • If overused, it can lead to maintenance issues because the effect of changes are known at runtime.
  • Dependency injection in java hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.

下载依赖注入项目

在Java中,依赖注入模式就是这些。当我们控制着服务时,了解并使用它是很好的选择。

发表回复 0

Your email address will not be published. Required fields are marked *


广告
将在 10 秒后关闭
bannerAds