一. DI(依赖注入)基本概念

DI—Dependency Injection,即“依赖注入”:
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。

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

Java依赖注入似乎很难用理论来理解,因此我将举一个简单的例子,然后我们将看到如何使用依赖注入模式来实现应用程序中的松散耦合和可扩展性。

假设我们有一个消费应用程序EmailService来发送电子邮件。通常我们会像下面这样实现。

public class EmailService {public void sendEmail(String message, String receiver){//发送电子邮件的逻辑System.out.println("Email sent to "+receiver+ " with Message="+message);}
}

EmailService类包含将电子邮件消息发送到收件人电子邮件地址的逻辑。我们的应用程序代码如下所示。

public class MyApplication {private EmailService email = new EmailService();public void processMessages(String msg, String rec){//做一些msg验证,操作逻辑等this.email.sendEmail(msg, rec);}
}

我们的客户代码将使用MyApplicationclass发送电子邮件,如下所示。

public class MyLegacyTest {public static void main(String[] args) {MyApplication app = new MyApplication();app.processMessages("锦瑟无端五十弦", "李商隐@锦瑟");}
}

乍一看,上述实现似乎没有错。但是以上代码逻辑有一定的局限性。

MyApplication类负责初始化电子邮件服务,然后使用它。这导致硬编码的依赖性。如果将来想切换到其他高级电子邮件服务,则需要在MyApplication类中更改代码。这使我们的应用程序难以扩展,如果在多个类中使用电子邮件服务,则将更加困难。

如果我们想扩展应用程序以提供附加的消息传递功能,例如SMS或Facebook消息,那么我们需要为此编写另一个应用程序。这将涉及应用程序类和客户端类中的代码更改。

由于我们的应用程序是直接创建电子邮件服务实例,因此测试该应用程序将非常困难。我们无法在测试类中模拟这些对象。

我们可以将MyApplication通过使用需要电子邮件服务作为参数的构造函数来从类中删除电子邮件服务实例的创建。

public class MyApplication {private EmailService email = null;public MyApplication(EmailService svc) {this.email = svc;}public void processMessages(String msg, String rec){//做一些msg验证,操作逻辑等this.email.sendEmail(msg, rec);}
}

但是在这种情况下,我们要求客户端应用程序或测试类初始化电子邮件服务,这不是一个好的设计决定。

现在让我们看看如何应用Java依赖注入模式来解决上述实现的所有问题。Java中的依赖注入至少需要满足以下条件:

  1. 服务组件应使用基类或接口进行设计。最好选择为服务定义合同的接口或抽象类。
  2. 消费者类应根据服务接口编写。
  3. 先初始化服务的注入器类,然后初始化使用者类。

二. Java依赖注入–服务组件

对于我们的情况,我们可以MessageService声明为服务实现接口。

public interface MessageService {void sendMessage(String msg, String rec);
}

现在,我们有实现上述接口的电子邮件和SMS服务。

public class EmailServiceImpl implements MessageService {@Overridepublic void sendMessage(String msg, String rec) {//发送电子邮件的逻辑System.out.println("Email sent to " + rec + " with Message=" + msg);}
}
public class SMSServiceImpl implements MessageService {@Overridepublic void sendMessage(String msg, String rec) {//发送电子邮件的逻辑System.out.println("SMS sent to " + rec + " with Message=" + msg);}
}

我们的依赖项注入Java服务已准备就绪,现在我们可以编写我们的使用者类。

三. Java依赖注入–服务使用者

我们不需要具有用于消费者类的基本接口,但是我将具有一个Consumer用于声明消费者类的接口。

public interface Consumer {void processMessages(String msg, String rec);
}

消费者类实现如下。

public class MyDIApplication implements Consumer{private MessageService service;public MyDIApplication(MessageService svc){this.service=svc;}@Overridepublic void processMessages(String msg, String rec){//做一些msg验证,操作逻辑等this.service.sendMessage(msg, rec);}}

请注意,我们的应用程序类仅在使用服务。它不会初始化导致更好的“关注点分离”的服务。服务接口的使用还使我们能够通过模拟MessageService轻松地测试应用程序,并在运行时而不是在编译时绑定服务。

现在我们准备编写java依赖注入程序类,该类将初始化服务以及使用者类。

四. Java依赖注入–注入器类

让我们有一个MessageServiceInjector带有方法声明的接口,该接口返回Consumer该类。

public interface MessageServiceInjector {public Consumer getConsumer();
}

现在,对于每项服务,我们将必须创建如下所示的注入器类。

public class EmailServiceInjector implements MessageServiceInjector {@Overridepublic Consumer getConsumer() {return new MyDIApplication(new EmailServiceImpl());}
}
public class SMSServiceInjector implements MessageServiceInjector {@Overridepublic Consumer getConsumer() {return new MyDIApplication(new SMSServiceImpl());}
}

现在,让我们看看我们的客户端应用程序将如何通过一个简单的程序使用该应用程序。

public class MyMessageDITest {public static void main(String[] args) {String msg = "锦瑟无端五十弦,一弦一柱思华年。";String email = "李商隐@锦瑟.com";String phone = "4088888888";MessageServiceInjector injector = null;Consumer app = null;//发送电子邮件injector = new EmailServiceInjector();app = injector.getConsumer();app.processMessages(msg, email);//Send SMSinjector = new SMSServiceInjector();app = injector.getConsumer();app.processMessages(msg, phone);}
}

如您所见,我们的应用程序类仅负责使用服务。服务类别是在注入器中创建的。同样,如果我们必须进一步扩展我们的应用程序以允许Facebook消息传递,我们将只必须编写Service类和注入器类。

因此,依赖项注入实现解决了具有硬编码依赖项的问题,并帮助我们使应用程序灵活且易于扩展。现在让我们看看通过模拟注入器和服务类来测试应用程序类有多么容易。

五. 带有模拟注入器和服务的JUnit测试用例

public class MyDIApplicationJUnitTest {private MessageServiceInjector injector;@Beforepublic void setUp() {//使用匿名类模拟注入器injector = new MessageServiceInjector() {@Overridepublic Consumer getConsumer() {//模拟消息服务return new MyDIApplication(new MessageService() {@Overridepublic void sendMessage(String msg, String rec) {System.out.println("模拟消息服务实现");}});}};}@Testpublic void test() {Consumer consumer = injector.getConsumer();consumer.processMessages("锦瑟无端五十弦", "李商隐@锦瑟");}@Afterpublic void tear() {injector = null;}
}

正如你可以看到,我使用匿名类来模拟注入器和服务类,我可以轻松地测试我的应用程序的方法。我在上述测试类中使用的是JUnit 4,因此,如果您在测试类之上运行,请确保它在您的项目构建路径中。

我们使用构造函数将依赖项注入应用程序类中,另一种方法是使用setter方法将依赖项注入应用程序类中。对于setter方法的依赖注入,我们的应用程序类将如下实现。

public class MyDIApplication implements Consumer {private MessageService service;public MyDIApplication(){}//setter依赖注入public void setService(MessageService service) {this.service = service;}@Overridepublic void processMessages(String msg, String rec){//做一些msg验证,操作逻辑等this.service.sendMessage(msg, rec);}
}
public class EmailServiceInjector implements MessageServiceInjector {@Overridepublic Consumer getConsumer() {MyDIApplication app = new MyDIApplication();app.setService(new EmailServiceImpl());return app;}
}

使用基于构造函数的依赖项注入还是基于setter是设计决策,取决于您的要求。例如,如果没有服务类我的应用程序根本无法工作,那么我宁愿使用基于构造函数的DI,否则我会选择基于setter方法的DI仅在真正需要时才使用它。

Java中的依赖注入是一种通过将对象绑定从编译时移到运行时来在我们的应用程序中实现控制反转(IoC)的方法。我们也可以通过工厂模式,模板方法设计模式,策略模式和服务定位器模式来实现IoC 。

Spring Dependency Injection, Google Guice和Java EE CDI框架通过使用Java Reflection API和Java注释简化了依赖项注入的过程。我们所需要做的就是注释字段,构造函数或设置方法,并在配置xml文件或类中对其进行配置。

六. Java依赖注入的优缺点

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

  1. 关注点分离
  2. 减少应用程序类中的代码,因为初始化依赖项的所有工作都由注入器组件处理
  3. 可配置的组件使应用程序易于扩展
  4. 使用模拟对象可以轻松进行单元测试

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

  1. 如果使用过度,则可能导致维护问题,因为在运行时知道更改的效果。
  2. Java中的依赖关系注入隐藏了服务类依赖关系,这可能导致运行时错误,而这些错误在编译时就会被捕获。

参考链接: DI设计模式示例教程

DI(依赖注入)设计模式相关推荐

  1. 【Java从0到架构师】Spring - IoC 控制反转、DI 依赖注入

    IoC 控制反转.DI 依赖注入 Spring 简介 Spring 基本使用 - IoC 容器 依赖注入 (Dependency Injection) 基于 setter 的注入 - 自定义对象 be ...

  2. phalapi可以依赖注入么_phalapi-进阶篇2(DI依赖注入和单例模式)

    phalapi-进阶篇2(DI依赖注入和单例模式) 前言 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 离上一次更新过去了快两周,在其中编写了一个关于DB ...

  3. phalapi可以依赖注入么_[7.8]-phalapi-进阶篇2(DI依赖注入和单例模式) | PhalApi(π框架) - PHP轻量级开源接口框架...

    phalapi-进阶篇2(DI依赖注入和单例模式) 前言 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. 离上一次更新过去了快两周,在其中编写了一个关于DB ...

  4. .net程序开发IOC控制反转和DI依赖注入详解

    大部分应用程序都是这样编写的:编译时依赖关系顺着运行时执行的方向流动,从而生成一个直接依赖项关系图. 也就是说,如果类 A 调用类 B 的方法,类 B 调用 C 类的方法,则在编译时,类 A 将取决于 ...

  5. Spring DI依赖注入讲解

    DI:dependency injection 依赖注入 在spring框架负责创建Bean对象时,动态将依赖对象注入到Bean组件. public class UserServiceImpl imp ...

  6. Spring DI(依赖注入)

    DI依赖注入 IoC(Inversion Of Control)控制翻转,Spring反向控制应用程序所需要使用的外部资源 DI(Dependency Injection)依赖注入,应用程序运行依赖的 ...

  7. Spring DI(依赖注入)注解篇

    1 课程内容介绍 我之前写的的一篇博客Spring核心功能DI(依赖注入)xml篇主要介绍了如何通过配置xml的方式来实现依赖注入,今天我们来介绍如何通过注解方式完成我们的依赖注入操作. 2 注入基本 ...

  8. Spring DI(依赖注入)Xml篇

    1 DI(依赖注入)简单介绍 如果您对Spring了解甚少,建议先移步我的另一篇博客Spring核心功能IOC之HelloWorld因为下面的内容是在该文章基础上进行阐述的 .我们可以通过一段简单代码 ...

  9. AutoFac IoC DI 依赖注入

    AutoFac IoC DI 依赖注入 记录点点滴滴知识,为了更好的服务后来者! 一.为什么使用AutoFac? 之前介绍了Unity和Ninject两个IOC容器,但是发现园子里用AutoFac的貌 ...

最新文章

  1. img标签动态绑定本地图片地址不生效
  2. React开发中常用的工具集锦
  3. 前端学习(2866):自定义组件库效果演示
  4. 提高数据库查询速度的几个思路
  5. 未预期的符号 `( 附近有语法错误_苹果iOS 14.2现在提供117种新的表情符号和新的壁纸...
  6. C# 查农历 阴历 阳历 公历 节假日
  7. 二十四、K8s集群强化2-授权
  8. delphi xe 10.4 开发 APP
  9. Logit模型和Logistic模型
  10. 数据库基础知识之数据类型
  11. 全球ATV DTV制式的分布
  12. 同一个jar包不同版本冲突解决方法
  13. OpenCV函数subtract()使用心得及需要注意的地方
  14. 图片预加载学习(二):有序加载之图片切换
  15. apa引用要在文中吗_APA写作规范究竟有多细节?
  16. 【UML】用例图、活动图、类图、顺序图练习题
  17. 用遗传算法,开启研究车间调度问题之门
  18. Hibernate:could not execute query 列名无效
  19. 1、WorkflowSim 工作流仿真软件介绍
  20. 简述java的跨平台性是什么,为什么具有跨平台性

热门文章

  1. OPENLDAP关于ldapsearch导出含中文的数据被加密的问题解决
  2. 【C#】使用ffmpeg image2pipe将图片保存为mp4视频
  3. 经典回溯算法(八皇后问题)详解
  4. UE4 粒子矢量场绘制(Maya2016)
  5. java使用zxing生成二维码,可带logo和底部文字
  6. alin的学习之路(Linux系统编程:三)(vim,gcc,库,gdb)
  7. linux crc 函数,Linux/CRC校验
  8. Hello, Cocos2d-x
  9. 一个睡5分钟等于6个钟头的方法!!!
  10. [UE4]使用Is Locally Controlled解决第一人称和第三人称武器位置问题