美文网首页
依赖注入

依赖注入

作者: HoooChan | 来源:发表于2018-05-21 17:53 被阅读9次

依赖

如果 Class A 中有 Class B 的一个实例,那么可以说 Class A 对 Class B 有一个依赖。比如下面类 Home 中用到一个 Father 对象,我们就说 Home 对 Father 有一个依赖。

public class Home {
    ...
    Father father;
    ...
    public Home() {
        father = new Father();
    }
}

这段代码存在的问题:
1、如果现在要修改Father对象的生成方式,如需要通过new Father(String Name)来实例化Father,那就也要修改Home的代码;
2、想测试不同Father对象对Home的影响很困难,因为father的初始化被写死在Human的构造函数中;
3、如果new Father()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象 Mock 掉这个过程也很困难。

这种在一个类中直接创建另一个类的对象的代码,被称为硬初始化(Hard init),和硬编码(hard-coded strings)以及硬编码的数字(magic numbers)一样,是一种导致耦合的坏味道。

依赖注入

public class Home {
    ...
    Father father;
    ...
    public Home(Father father) {
        this.father = father;
    }
}

像上面这种不是自己主动初始化依赖,而通过外部传来依赖的方式,就称为依赖注入。

下面是几种实现依赖注入的方式:

构造函数注入

public class Home {
    ...
    Father father;
    ...
    public Home(Father father) {
        this.father = father;
    }
}

这应该是最简单的依赖注入方式。Father对象在Home类之外创建,再传进来。

setter注入

public class Home {
    ...
    Father father;
    ...
    public void setFather(Father father) {
        this.father = father;
    }
}

接口注入

创建一个接口

public interface InjectFather {
    void injectFather(Father father);
}

实现这个接口

class Home implements InjectFather {
    ...
    public void InjectFather (Father father) {
      this.father = father;
    }
    ...
}

在Java中实现依赖注入

在Java中实现依赖注入除了以上说的几种,还有一种最常用的是使用注解。

public class Home {
    ...
    @Inject 
    Father father;
    ...
    public Home() {
    }

这种方式需要使用依赖注入框架,并进行简单的配置。现在 Java 语言中较流行的依赖注入框架有 Google Guice、Spring 等,而在 Android 上比较流行的有 RoboGuice、Dagger 等。

Dagger

Dagger是为Android和Java平台提供的一个完全静态的,在编译时进行依赖注入的框架,原来是由Square公司维护,现在由Google维护。 Dagger就是用来创造一个容器,所有需要被依赖的对象在Dagger的容器中实例化,并通过Dagger注入到合适的地方。

Dagger的基本使用

通常情况下引用一个类的做法
先定义一个简单的类

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在另一个类中对其引用

public class Activity{

    private void initData() {
        User user = new User();
        user.setName("测试");
     }
}

Dagger2的做法
首先在被依赖类的构造函数用@Inject标注

public class User {
    private String name;

    @Inject
    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

创建一个@Component标注的注入器接口,并在注入器中使用 void inject(MainActivity MainActivity);来表明哪里要用到注入器

@Component
public interface ActivityComponent {
 
     void inject(MainActivity MainActivity);
}

在MainActivity中对注入器进行初始化DaggerActivityComponent.builder().build().inject(this); 初始化后该注入器就可以正常使用了;在MainActivity中对需要注入的类 User用@Inject进行标注,表示该类需要被注入,即实例化

public class Activity{

    @Inject
    User user;

    private void initData() {
        //DaggerActivityComponent是注入器是在编译的过程中生成的
        DaggerActivityComponent.builder().build().inject(this);
        user.setName("测试");
     }
}
  • @Inject 主要有两个作用,一个是使用在构造函数上,通过标记构造函数让Dagger2来使用从而提供依赖,另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖。
  • @Component 一般用来标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入到其中。

@Module和@Provide

@Inject是对类的构造函数进行标注来进行实例化的,但是有些类,比如第三方OkHttpClient,我们是无法对其源码进行修改的即对其构造函数进行标注,这个时候我们就用到了@Module。

@Module是和@Component配合使用的,意思就是告诉注入器,如果你在实例化对象的时候,没有找到合适的构造函数,你就来我这里找,@Module通常标注一个类,该类里面可以实例化各种类,Component在注入对象的时候先去Module中找,如果找不到就会检查所有被@Inject标注的构造函数

@Module
public class ActivityMoudle {

    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        Interceptor apikey = chain -> chain.proceed(chain.request().newBuilder()
                .addHeader("apikey", Constants.Api_Key).build());

        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .readTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(apikey)
                .addInterceptor(loggingInterceptor)
                .build();

        return okHttpClient;
    }

}
  • @Provide 用来标注一个方法,告诉注入器,我标注的方法你可以用来提供实例;

修改注入器

@Singleton
@Component(modules = ActivityMoudle.class)
public interface ActivityComponent {

    void inject(MainActivity MainActivity);
}

Dagger原理浅析

我们先新建三个类,这三个类的内容和上面的一致: 编译之后生成了对应的三个类:

我们看DaggerActivityComponent的内容,其实就是实现了我们定义的ActivityComponent接口:

package com.hochan.myapplication;

import dagger.MembersInjector;
import dagger.internal.MembersInjectors;
import javax.annotation.Generated;

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerActivityComponent implements ActivityComponent {
  private MembersInjector<MainActivity> mainActivityMembersInjector;

  private DaggerActivityComponent(Builder builder) {  
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {  
    return new Builder();
  }

  public static ActivityComponent create() {  
    return builder().build();
  }

  private void initialize(final Builder builder) {  
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), User_Factory.create());
  }

  @Override
  public void myInject(MainActivity activity) {  
    mainActivityMembersInjector.injectMembers(activity);
  }

  public static final class Builder {
    private Builder() {  
    }
  
    public ActivityComponent build() {  
      return new DaggerActivityComponent(this);
    }
  }
}

可以看到我们在接口中定义的myInject()方法:

  @Override
  public void myInject(MainActivity activity) {  
    mainActivityMembersInjector.injectMembers(activity);
  }

就是在这个方法中把变量注入到Mainactivity中。

再看mainActivityMembersInjector这个变量,其实就是一个MembersInjector接口,其初始化在initialize()中完成:

  private void initialize(final Builder builder) {  
    this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), User_Factory.create());
  }

而MainActivity_MembersInjector就是生成的另外一个类,这个类就是一个注入器。有多少个类依赖了其他的类,就有多少个这样的注入器类生成。在这个类的injectMembers()方法中完成了对象的注入:

  @Override
  public void injectMembers(MainActivity instance) {  
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    supertypeInjector.injectMembers(instance);
    instance.mUser = mUserProvider.get();
  }

还有一个编译之后生成的类User_Factory,就是一个对象的提供者。

@Generated("dagger.internal.codegen.ComponentProcessor")
public enum User_Factory implements Factory<User> {
INSTANCE;

  @Override
  public User get() {  
    return new User();
  }

  public static Factory<User> create() {  
    return INSTANCE;
  }
}

这里User的构造函数是没有参数的,我们把它改成有参数再编译看会变成什么:

    @Inject
    public User(String name){
    }

编译之后报错,User_Factory多了一个参数nameProvider:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class User_Factory implements Factory<User> {
  private final Provider<String> nameProvider;

  public User_Factory(Provider<String> nameProvider) {  
    assert nameProvider != null;
    this.nameProvider = nameProvider;
  }

  @Override
  public User get() {  
    return new User(nameProvider.get());
  }

  public static Factory<User> create(Provider<String> nameProvider) {  
    return new User_Factory(nameProvider);
  }
}

get()函数变成了:

  @Override
  public User get() {  
    return new User(nameProvider.get());
  }
而在DaggerActivityComponent中的初始化并没有把这个参数传进来:

从上面的介绍我们可以知道,可以通过module来让Dagger找到获取实例的方法。这里我们新建一个module来让Dagger获得初始化User要用到的name字符串:

@Module
public class ActivityModule {

    @Provides
    String myName(){
        return "names";
    }
}

然后在ActivityComponent中声明这个类:

@Component(modules = ActivityModule.class)
public interface ActivityComponent {

    void myInject(MainActivity activity);
}

之后就运行通过了,我们会发现编译之后又多了一个类:ActivityModule_MyNameFactory

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ActivityModule_MyNameFactory implements Factory<String> {
  private final ActivityModule module;

  public ActivityModule_MyNameFactory(ActivityModule module) {  
    assert module != null;
    this.module = module;
  }

  @Override
  public String get() {  
    String provided = module.myName();
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<String> create(ActivityModule module) {  
    return new ActivityModule_MyNameFactory(module);
  }
}

在get()方法里通过ActivityModule调用定义好的方法来获取到name的值。这里定位那个函数是获取name的函数应该是通过返回的类型来判断的,因为如果在ActivityModule中有多个返回String的函数就会报错。

相关文章

  • 开源项目的依赖注入

    开源项目的依赖注入 依赖注入概念 依赖注入(DI:Dependency Injection): 依赖注入方式: B...

  • 资料收集

    依赖注入 AngularJs依赖注入的研究 (已读) 依赖注入(已读)

  • Dagger2常用注解诠释

    依赖注入 控制反转(IoC)与依赖注入(DI)浅谈依赖注入理解依赖注入(IOC)和学习Unity Gradle配置...

  • Day62 Spring 依赖注入源码解析

    手动注入自动注入 依赖注入,set注入,构造注入 依赖注入: Key依据 byType byName constr...

  • Dagger2 源码分析

    Dagger简单介绍 Dagger2涉及到依赖注入,有关依赖注入的请到理解依赖注入 通过注解方式实现依赖注入分为两...

  • 依赖注入(转)

    依赖注入(转) 原文地址:依赖注入原理

  • Spring学习之依赖注入

    Spring学习之依赖注入 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(...

  • 依赖注入及Dagger2框架简介

    依赖注入简介 在介绍Dagger框架之前我们先来看看依赖注入(Dependence Injection),依赖注入...

  • 浅谈依赖注入

    依赖注入是什么? 依赖注入的作用是什么? 依赖注入的应用场景? 如何实现依赖注入? 对于一个后端程序员来说,依赖注...

  • 依赖注入的方式

    依赖注入: 依赖于某些方式给Bean的资源进行注入 Spring 支持三种依赖注入的方式 属性注入 构造器注入 工...

网友评论

      本文标题:依赖注入

      本文链接:https://www.haomeiwen.com/subject/vyjqjftx.html