前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java依赖注入(DI)实例详解

Java依赖注入(DI)实例详解

作者头像
青山师
发布2023-05-04 20:52:39
5470
发布2023-05-04 20:52:39
举报
文章被收录于专栏:IT当时语_青山师_JAVA技术栈

Java依赖注入模式允许我们摆脱硬编码,使我们的应用更加松耦合、增强扩展性以及可维护性。通过依赖注入我们可以降低从编译到运行时的依赖性。

Java依赖注入

Java的依赖注入仅仅通过理论是很难解明白的,所以我们通过几个简单的示例来描述它,怎样利用依赖注入模式降低我们应用之间的耦合性和增强可扩展性。

假设我们的应用需要通过 EmailService 去发送email,通常情况下,我们是这样实现的:

代码语言:javascript
复制
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的服务:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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

代码语言:javascript
复制
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 来指定服务规范。

代码语言:javascript
复制
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 的两种发送消息的服务实现。

代码语言:javascript
复制
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);
    }

}
代码语言:javascript
复制
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 。

代码语言:javascript
复制
package com.byron4j.hightLevel.java8.pattern.di.pattern;

/**
 * <pre>
 *      制定消费服务的规范
 * </pre>
 * @author Byron.Y.Y
 */
public interface Consumer {
    void processMessages(String msg, String rec);
}

以及消费的具体实现:

代码语言:javascript
复制
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 的方法。

代码语言:javascript
复制
package com.byron4j.hightLevel.java8.pattern.di.pattern;


/**
 * 
 * <pre>
 *      依赖注入服务
 * </pre>
 * @author Byron.Y.Y
 */
public interface MessageServiceInjector {

    /**获得消费类*/
    public Consumer getConsumer();

}

现在为每一个服务,我们都可以创建其依赖注入类了:

代码语言:javascript
复制
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());
    }

}
代码语言:javascript
复制
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());
    }

}

现在,在客户端的简单实用实例如下:

代码语言:javascript
复制
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注入服务

代码语言:javascript
复制
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方法来注入服务

代码语言:javascript
复制
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);
    }   

}
代码语言:javascript
复制
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依赖注入的缺陷
  • 滥用有可能难以维护,因为很多错误都从编译器转移到了运行时
  • 依赖注入隐藏了服务类的依赖,可能导致运行时错误,而这之前是可能在编译器就能发现的
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-05-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java依赖注入
    • Java依赖注入—-Service组件
      • Java依赖注入—-服务调用者(消费者)
        • Java依赖注入—-注入类
          • Java依赖注入—-单元测试MOCK注入服务
          • 依赖注入总结
          相关产品与服务
          腾讯云服务器利旧
          云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档