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依赖注入的缺陷

  • 滥用有可能难以维护,因为很多错误都从编译器转移到了运行时

  • 依赖注入隐藏了服务类的依赖,可能导致运行时错误,而这之前是可能在编译器就能发现的

0 0
原创粉丝点击