依赖注入(di)模式
Java Dependency Injectiondesign pattern allows us to remove the hard-coded dependencies and make our application loosely coupled, extendable and maintainable. We can implementdependency injection in javato move the dependency resolution from compile-time to runtime.
Java Dependency Injection设计模式允许我们删除硬编码的依赖关系,并使我们的应用程序松散耦合,可扩展和可维护。 我们可以在Java中实现依赖注入,以将依赖解析从编译时移至运行时。
Java依赖注入 (Java Dependency Injection)
Java Dependency injection seems hard to grasp with theory, so I would take a simple example and then we will see how to use dependency injection pattern to achieve loose coupling and extendability in the application.
Java依赖注入似乎很难用理论来理解,因此我将举一个简单的例子,然后我们将看到如何使用依赖注入模式来实现应用程序中的松散耦合和可扩展性。
Let’s say we have an application where we consumeEmailService
to send emails. Normally we would implement this like below.
假设我们有一个应用程序,我们在其中使用EmailService
发送电子邮件。 通常我们会像下面这样实现。
package com.journaldev.java.legacy;public class EmailService {public void sendEmail(String message, String receiver){//logic to send emailSystem.out.println("Email sent to "+receiver+ " with Message="+message);}}
EmailService
class holds the logic to send an email message to the recipient email address. Our application code will be like below.
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 etcthis.email.sendEmail(msg, rec);}}
Our client code that will useMyApplication
class to send email messages will be like below.
我们的客户端代码将使用MyApplication
类发送电子邮件,如下所示。
package com.journaldev.java.legacy;public class MyLegacyTest {public static void main(String[] args) {MyApplication app = new MyApplication();app.processMessages("Hi Pankaj", "pankaj@");}}
At first look, there seems nothing wrong with the above implementation. But above code logic has certain limitations.
乍一看,上述实现似乎没有错。 但是以上代码逻辑有一定的局限性。
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.MyApplication
类负责初始化电子邮件服务,然后使用它。 这导致硬编码的依赖性。 如果将来想切换到其他高级电子邮件服务,则需要在MyApplication类中更改代码。 这使我们的应用程序难以扩展,如果在多个类中使用电子邮件服务,则将更加困难。 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.如果我们想扩展应用程序以提供其他消息传递功能,例如SMS或Facebook消息,那么我们需要为此编写另一个应用程序。 这将涉及应用程序类和客户端类中的代码更改。 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.由于我们的应用程序是直接创建电子邮件服务实例,因此测试该应用程序将非常困难。 我们无法在测试类中模拟这些对象。
One can argue that we can remove the email service instance creation fromMyApplication
class by having a constructor that requires email service as an argument.
可以说,我们可以通过使用需要电子邮件服务作为参数的构造函数来从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 etcthis.email.sendEmail(msg, rec);}}
But in this case, we are asking client applications or test classes to initializing the email service that is not a good design decision.
但是在这种情况下,我们要求客户端应用程序或测试类初始化电子邮件服务,这不是一个好的设计决定。
Now let’s see how we can apply java dependency injection pattern to solve all the problems with the above implementation. Dependency Injection in java requires at least the following:
现在,让我们看看如何应用Java依赖注入模式来解决上述实现的所有问题。 Java中的依赖注入至少需要满足以下条件:
Service components should be designed with base class or interface. It’s better to prefer interfaces or abstract classes that would define contract for the services.服务组件应使用基类或接口进行设计。 最好选择为服务定义合同的接口或抽象类。 Consumer classes should be written in terms of service interface.消费者类应根据服务接口编写。 Injector classes that will initialize the services and then the consumer classes.将初始化服务的注入器类,然后将初始化使用者类。
Java依赖注入–服务组件 (Java Dependency Injection – Service Components)
For our case, we can haveMessageService
that will declare the contract for service implementations.
对于我们的情况,我们可以使用MessageService
来声明服务实现的合同。
package com.journaldev.java.dependencyinjection.service;public interface MessageService {void sendMessage(String msg, String rec);}
Now let’s say we have Email and SMS services that implement the above interfaces.
现在,我们有实现上述接口的电子邮件和SMS服务。
package com.journaldev.java.dependencyinjection.service;public class EmailServiceImpl implements MessageService {@Overridepublic void sendMessage(String msg, String rec) {//logic to send emailSystem.out.println("Email sent to "+rec+ " with Message="+msg);}}
package com.journaldev.java.dependencyinjection.service;public class SMSServiceImpl implements MessageService {@Overridepublic void sendMessage(String msg, String rec) {//logic to send SMSSystem.out.println("SMS sent to "+rec+ " with Message="+msg);}}
Our dependency injection java services are ready and now we can write our consumer class.
我们的依赖项注入Java服务已准备就绪,现在我们可以编写我们的使用者类。
Java依赖注入–服务使用者 (Java Dependency Injection – Service Consumer)
We are not required to have base interfaces for consumer classes but I will have aConsumer
interface declaring contract for consumer classes.
我们不需要具有用于消费者类的基本接口,但是我将具有一个用于声明消费者类合同的Consumer
接口。
package com.journaldev.java.dependencyinjection.consumer;public interface Consumer {void processMessages(String msg, String rec);}
My consumer class implementation is like below.
我的消费者类实现如下。
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;}@Overridepublic void processMessages(String msg, String rec){//do some msg validation, manipulation logic etcthis.service.sendMessage(msg, rec);}}
Notice that our application class is just using the service. It does not initialize the service that leads to better “separation of concerns“. Also use of service interface allows us to easily test the application by mocking the MessageService and bind the services at runtime rather than compile time.
请注意,我们的应用程序类仅在使用服务。 它不会初始化导致更好的“关注点分离”的服务。 服务接口的使用还使我们能够通过模拟MessageService轻松测试应用程序,并在运行时(而不是在编译时)绑定服务。
Now we are ready to writejava dependency injector classesthat will initialize the service and also consumer classes.
现在,我们准备编写java依赖注入程序类,该类将初始化服务以及使用者类。
Java依赖注入–注入器类 (Java Dependency Injection – Injectors Classes)
Let’s have an interfaceMessageServiceInjector
with method declaration that returns theConsumer
class.
让我们有一个带有方法声明的MessageServiceInjector
接口,该方法声明返回Consumer
类。
package com.journaldev.java.dependencyinjection.injector;import com.journaldev.java.dependencyinjection.consumer.Consumer;public interface MessageServiceInjector {public Consumer getConsumer();}
Now for every service, we will have to create injector classes like below.
现在,对于每项服务,我们将必须创建如下所示的喷射器类。
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 {@Overridepublic 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 {@Overridepublic Consumer getConsumer() {return new MyDIApplication(new SMSServiceImpl());}}
Now let’s see how our client applications will use the application with a simple program.
现在,让我们看看我们的客户端应用程序将如何通过一个简单的程序使用该应用程序。
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@";String phone = "4088888888";MessageServiceInjector injector = null;Consumer app = null;//Send emailinjector = new EmailServiceInjector();app = injector.getConsumer();app.processMessages(msg, email);//Send SMSinjector = new SMSServiceInjector();app = injector.getConsumer();app.processMessages(msg, phone);}}
As you can see that our application classes are responsible only for using the service. Service classes are created in injectors. Also if we have to further extend our application to allow facebook messaging, we will have to write Service classes and injector classes only.
如您所见,我们的应用程序类仅负责使用服务。 服务类别是在注射器中创建的。 同样,如果我们必须进一步扩展我们的应用程序以允许Facebook消息传递,我们将只必须编写Service类和注射器类。
So dependency injection implementation solved the problem with hard-coded dependency and helped us in making our application flexible and easy to extend. Now let’s see how easily we can test our application class by mocking the injector and service classes.
因此,依赖项注入实现解决了具有硬编码依赖项的问题,并帮助我们使应用程序灵活且易于扩展。 现在,让我们看看通过模拟注入器和服务类来测试应用程序类有多么容易。
Java依赖注入–带有模拟注入器和服务的JUnit测试用例 (Java Dependency Injection – JUnit Test Case with Mock Injector and Service)
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;@Beforepublic void setUp(){//mock the injector with anonymous classinjector = new MessageServiceInjector() {@Overridepublic Consumer getConsumer() {//mock the message servicereturn new MyDIApplication(new MessageService() {@Overridepublic void sendMessage(String msg, String rec) {System.out.println("Mock Message Service implementation");}});}};}@Testpublic void test() {Consumer consumer = injector.getConsumer();consumer.processMessages("Hi Pankaj", "pankaj@");}@Afterpublic void tear(){injector = null;}}
As you can see that I am using anonymous classes tomock the injector and service classesand I can easily test my application methods. I am using JUnit 4 for the above test class, so make sure it’s in your project build path if you are running above test class.
如您所见,我正在使用匿名类模拟注入器和服务类,并且可以轻松测试我的应用程序方法。 我在上述测试类中使用的是JUnit 4,因此,如果您在测试类之上运行,请确保它在您的项目构建路径中。
We have used constructors to inject the dependencies in the application classes, another way is to use a setter method toinject dependenciesin application classes. For setter method dependency injection, our application class will be implemented like below.
我们使用构造函数将依赖项注入应用程序类中,另一种方法是使用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 injectionpublic void setService(MessageService service) {this.service = service;}@Overridepublic void processMessages(String msg, String rec){//do some msg validation, manipulation logic etcthis.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 {@Overridepublic Consumer getConsumer() {MyDIApplication app = new MyDIApplication();app.setService(new EmailServiceImpl());return app;}}
One of the best example of setter dependency injection is Struts2 Servlet API Aware interfaces.
设置程序依赖项注入的最佳示例之一是Struts2 Servlet API Aware接口 。
Whether to use Constructor based dependency injection or setter based is a design decision and depends on your requirements. For example, if my application can’t work at all without the service class then I would prefer constructor based DI or else I would go for setter method based DI to use it only when it’s really needed.
使用基于构造函数的依赖项注入还是基于setter是设计决策,取决于您的要求。 例如,如果我的应用程序如果没有服务类就无法工作,那么我会更喜欢基于构造函数的DI,否则我会选择基于setter方法的DI来仅在确实需要它时才使用它。
Dependency Injection in Javais a way to achieveInversion of control(IoC) in our application by moving objects binding from compile time to runtime. We can achieve IoC through Factory Pattern, Template Method Design Pattern, Strategy Pattern and Service Locator pattern too.
Java中的依赖注入是一种通过将对象绑定从编译时移到运行时来在我们的应用程序中实现控制反转(IoC)的方法。 我们也可以通过工厂模式 , 模板方法设计模式 , 策略模式和服务定位器模式来实现IoC。
Spring Dependency Injection,Google GuiceandJava EE CDIframeworks facilitate the process of dependency injection through use of Java Reflection API and java annotations. All we need is to annotate the field, constructor or setter method and configure them in configuration xml files or classes.
Spring Dependency Injection,Google Guice和Java EE CDI框架通过使用Java Reflection API和Java注释简化了依赖项注入的过程。 我们所需要做的就是注释字段,构造函数或设置方法,并在配置xml文件或类中对其进行配置。
Java依赖注入的好处 (Benefits of Java Dependency Injection)
Some of the benefits of using Dependency Injection in Java are:
在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依赖注入的缺点 (Disadvantages of Java Dependency Injection)
Java Dependency injection has some disadvantages too:
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中的依赖项注入隐藏了服务类依赖关系,这可能导致运行时错误,而这些错误在编译时就会被捕获。 Download Dependency Injection Project下载依赖注入项目
That’s all fordependency injection pattern in java. It’s good to know and use it when we are in control of the services.
这就是java中依赖注入模式的全部内容。 当我们控制服务时,很高兴知道并使用它。
翻译自: /2394/java-dependency-injection-design-pattern-example-tutorial
依赖注入(di)模式