Java依赖注入(DI)实例详解
来源:互联网 发布:isis有多残忍知乎 编辑:程序博客网 时间:2024/06/16 05:47
Java依赖注入模式允许我们摆脱硬编码,使我们的应用更加松耦合、增强扩展性以及可维护性。通过依赖注入我们可以降低从编译到运行时的依赖性。
Java依赖注入
Java的依赖注入仅仅通过理论是很难解明白的,所以我们通过几个简单的示例来描述它,怎样利用依赖注入模式降低我们应用之间的耦合性和增强可扩展性。
假设我们的应用需要通过 EmailService 去发送email,通常情况下,我们是这样实现的:
package com.byron4j.hightLevel.java8.pattern.di;/** * * <pre> * 通常用于发送emai的服务 * </pre> * @author Byron.Y.Y */public class EmailService { /* * 发送email的方法 */ public void sendEmail(String message, String receiver){ //发送email的业务逻辑 System.out.println("发送消息:" + message + "给接收者:" + receiver); }}
EmailService 类是提供发送email的服务类,在我们的应用中,可能会这样使用发送email的服务:
package com.byron4j.hightLevel.java8.pattern.di;/** * * <pre> * 我们的应用,利用EmailService来发送email * </pre> * @author Byron.Y.Y */public class MyApplication { private EmailService emailService = new EmailService(); public void processMessages(String msg, String rec){ //做一些信息验证、操作逻辑等等 this.emailService.sendEmail(msg, rec); }}
在我们的客户端,则可能使用我们的应用MyApplication 来处理email:
package com.byron4j.hightLevel.java8.pattern.di;/** * * <pre> * 调用应用提供的处理eamil的服务 * </pre> * @author Byron.Y.Y */public class MyLegacyTest { public static void main(String[] args) { MyApplication myApplication = new MyApplication(); //纯属虚拟,勿投诉 myApplication.processMessages("马云,你好!", "mayun@taobao.com"); }}
初窥以上代码,貌似没什么缺陷,但是业务逻辑上有几个限制。
MyApplication 类需要负责初始化emailService并且使用它。这样就导致了硬编码依赖。如果以后我们想使用其他更好的email服务进行发送email,我们不得不去修改MyApplication 的代码。这使得我们的应用难以扩展,如果emailService需要在更多的类中使用,可维护性则更差了。
如果我们需要扩展出其他的发送消息的方式如SMS、Facebook message等,迫使我们需要写一个其他的application,这需要服务端以及客户端都需要修改相关代码。
测试application将会变得很麻烦,因为我们的应用是直接创建emailService实例的。 我们根本无法在测试用例中MOCK出这个emailService对象。
一个较好的方案,我们可以不在MyApplication 中直接创建emailService实例,而是让那些需要使用该发送eamil服务的应用通过构造器的参数去设置emailService
package com.byron4j.hightLevel.java8.pattern.di;/** * * <pre> * 为那些需要使用email服务的应用提供专有的构造器 * </pre> * @author Byron.Y.Y */public class MyApplication4Constructor { private EmailService email = null; public MyApplication4Constructor(EmailService svc){ this.email=svc; } public void processMessages(String msg, String rec){ //做一些信息验证、操作逻辑等等 this.email.sendEmail(msg, rec); }}
尽管如此,我们还是得需要在客户端或者测试用例中去初始化emailService实例,显然这并不是我们所理想的。
现在,我们想想怎么利用Java DI依赖注入模式前面的问题……
1 服务组件需要设计成基类 or 接口( 实际中我们更多的是使用抽象类或者接口来规约服务规范 )
2 服务实现需要实现服务组件约定的服务规范
3 注入类Injector Class负责初始化服务以及服务实现
Java依赖注入—-Service组件
在这个设计中,我们使用 MessageService 来指定服务规范。
package com.byron4j.hightLevel.java8.pattern.di.pattern;/** * * <pre> * 该接口用于制定服务规范 * </pre> * @author Byron.Y.Y */public interface MessageService { /**发送消息的服务*/ void sendMessage(String msg, String rec);}
现在我们可以有Email和SMS 的两种发送消息的服务实现。
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageService;/** * * <pre> * 发送email的服务实现,实现了消息服务的规范 * </pre> * @author Byron.Y.Y */public class EmailServiceImpl implements MessageService { @Override public void sendMessage(String msg, String rec) { System.out.println("发送邮件给 "+rec+ " ,内容为:"+msg); }}
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageService;/** * * <pre> * 发送短信的服务实现 * </pre> * @author Byron.Y.Y */public class SMSServiceImpl implements MessageService { @Override public void sendMessage(String msg, String rec) { System.out.println("发送短信给 "+rec+ " ,内容为:"+msg); }}
我们的可用于依赖注入的服务实现已经开发完毕,接下来我们需要编写消费服务的类。
Java依赖注入—-服务调用者(消费者)
我们需要制定服务消费的规范Consumer 。
package com.byron4j.hightLevel.java8.pattern.di.pattern;/** * <pre> * 制定消费服务的规范 * </pre> * @author Byron.Y.Y */public interface Consumer { void processMessages(String msg, String rec);}
以及消费的具体实现:
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageService;/** * * <pre> * 服务消费的具体实现----使用构造器注入的方式 * </pre> * @author Byron.Y.Y */public class MyDIApplication implements Consumer { /**接口形式--多态--服务属性*/ MessageService messageService; /**构造器注入服务属性*/ public MyDIApplication(MessageService messageService) { super(); this.messageService = messageService; } @Override public void processMessages(String msg, String rec) { //发送消息,取决于具体的服务实现的行为 this.messageService.sendMessage(msg, rec); }}
请注意,上面我们仅仅是使用到了service,并没有初始化它,尽量达到“关注点分离”—– 对于我来说我仅仅是使用它这就是我能做且只能做的分内事,那么我不应该去生成它那不是我的职责范围另外,使用接口服务的形式,我们可以更好的测试应用,MOCK MessageService 并在运行时绑定service而不是在编译期。
现在,我们可以编写Java依赖注入类了——–用来初始化service、consumer
Java依赖注入—-注入类
我们编写一个MessageServiceInjector 接口,声明一个获得Consumer 的方法。
package com.byron4j.hightLevel.java8.pattern.di.pattern;/** * * <pre> * 依赖注入服务 * </pre> * @author Byron.Y.Y */public interface MessageServiceInjector { /**获得消费类*/ public Consumer getConsumer();}
现在为每一个服务,我们都可以创建其依赖注入类了:
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageServiceInjector;/** * * <pre> * email服务的依赖注入类 * </pre> * @author Byron.Y.Y */public class EmailServiceInjector implements MessageServiceInjector { @Override public Consumer getConsumer() { return new MyDIApplication(new EmailServiceImpl()); }}
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageServiceInjector;/** * * <pre> * SMS服务的依赖注入类 * </pre> * @author Byron.Y.Y */public class SMSServiceInjector implements MessageServiceInjector { @Override public Consumer getConsumer() { return new MyDIApplication(new SMSServiceImpl()); }}
现在,在客户端的简单实用实例如下:
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageServiceInjector;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); }}
运行结果如下:
发送邮件给 pankaj@abc.com ,内容为:Hi Pankaj
发送短信给 4088888888 ,内容为:Hi Pankaj
至此,你会发现我们的application仅仅负责使用service。
So,依赖注入解决硬编码问题,使我们的应用变得更加灵活易扩展了。
再来看看我们的测试如何更加容易MOCK了吧。
Java依赖注入—-单元测试MOCK注入服务
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import org.junit.After;import org.junit.Before;import org.junit.Test;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageService;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageServiceInjector;/** * * <pre> * 单元测试类,MOCK服务,负责注入 * </pre> * @author Byron.Y.Y */public class MyDIApplicationJUnitTest { /**注入类*/ MessageServiceInjector injector ; @Before public void setUp(){ injector = new MessageServiceInjector() { @Override public Consumer getConsumer() { return new MyDIApplication( new MessageService() { @Override public void sendMessage(String msg, String rec) { System.out.println("Mock Message Service implementation"); } }); } }; } @Test public void testInjector(){ Consumer consumer = injector.getConsumer(); consumer.processMessages("Hi Pankaj", "pankaj@abc.com"); } @After public void clean(){ //回收 injector = null; }}
在上述测试类中,我们使用了匿名内部类来mock 注入器和服务,使得测试接口服务变得容易些。
我们是使用构造器来注入服务的、另外一种方式是在application类中使用setter方法来注入服务
package com.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageService;/** * * <pre> * 使用setter注入 * </pre> * @author Byron.Y.Y */public class MyDIApplication4Setter implements Consumer{ private MessageService service; public MyDIApplication4Setter(){} //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.byron4j.hightLevel.java8.pattern.di.pattern.impl;import com.byron4j.hightLevel.java8.pattern.di.pattern.Consumer;import com.byron4j.hightLevel.java8.pattern.di.pattern.MessageServiceInjector;/** * * <pre> * 属性注入的注入器 * </pre> * @author Byron.Y.Y */public class EmailServiceInjector4Setter implements MessageServiceInjector{ @Override public Consumer getConsumer() { MyDIApplication4Setter app = new MyDIApplication4Setter(); app.setService( new EmailServiceImpl() ); return app; }}
具体采用构造器注入还是setter注入方式,取决于你的需求。假如我的应用不能离开服务类而运作那么会采用构造器注入,否则采用setter注入方式。
依赖注入总结
依赖注入( DI )的方式可以达到控制反转( IOC )的目的,将对象从绑定从编译器转移到运行时。我们也可以通过工厂模式、模板模式或者策略模式等方式达到控制反转 ( IOC )。
Spring依赖注入、Google Guice和Java EE CDI框架通过反射、注解技术使得依赖注入变得更简单。我们要做的仅仅是在属性、构造器或setter中添加某些注解。
Java依赖注入的好处
关注点分离
减少样板代码,因为所有服务的初始化都是通过我们的注入器组件来完成的
可配置化的组件使得应用更易于扩展
单元测试更易于MOCK对象
Java依赖注入的缺陷
滥用有可能难以维护,因为很多错误都从编译器转移到了运行时
依赖注入隐藏了服务类的依赖,可能导致运行时错误,而这之前是可能在编译器就能发现的
- Java依赖注入(DI)实例详解
- Spring依赖注入(DI)详解
- Java IoC(控制反转)/DI(依赖注入)
- 详解Spring的依赖/依赖注入(DI)
- 详解Spring的依赖/依赖注入(DI)
- spring(依赖注入-DI)
- Spring DI 依赖注入
- DI依赖注入
- Spring-DI(依赖注入)
- Spring依赖注入DI
- Spring 依赖注入(DI)
- Spring DI[依赖注入]
- Spring DI(依赖注入)
- Spring依赖注入(DI)
- 依赖注入DI
- Spring依赖注入DI
- spring02依赖注入DI
- 依赖注入(DI)
- 利用两个栈实现队列的--->基本操作
- python2.7和Python3.5的区别
- 泛型
- codeforces 807C Success Rate 数论
- opencv读取图片失败
- Java依赖注入(DI)实例详解
- 迭代器、增强for
- SSM实现权限管理
- 用远程桌面访问服务器server2012
- Mybatis入门--缓存问题
- Java中的异常处理
- 8皇后问题求解
- Java调整图片大小合并PNG图片生成ICON(保持背景透明)
- 猴子吃桃问题