使用Dagger 2进行依赖注入 - API介绍

来源:互联网 发布:朽木白哉实力 知乎 编辑:程序博客网 时间:2024/06/07 13:47

原文链接:http://frogermcs.github.io/dependency-injection-with-dagger-2-the-api/

本文是展示在Android应用中使用Dagger2进行依赖注入的系列文章之一。今天我想深入研究Dagger2的基础,并遍历这个依赖注入框架的所有API。

Dagger 2

前文我提到了DI框架带给我们的好处 - 它用少量代码绑定了所有依赖关系。Dagger2是一个DI框架的例子,它生成了大量的代码模板。但是,为什么它更优秀呢?现在,它是唯一一个模拟用户手写自动生成完全可追溯源码的DI框架。这意味着依赖关系图就是魔法门。Dagger2相较其他注入框架缺少动态性(未使用反射),但生成代码的简单性和效率与手写代码无异。

Dagger 2基础

Dagger2 API如下:

public @interface Component {    Class<?>[] modules() default {};    Class<?>[] dependencies() default {};}public @interface Subcomponent {    Class<?>[] modules() default {};}public @interface Module {    Class<?>[] includes() default {};}public @interface Provides {}public @interface MapKey {    boolean unwrapValue() default true;}public interface Lazy<T> {    T get();}

还有其他几个JSR-330(Java中的依赖注入标准)定义的注解,也用在Dagger2中:

public @interface Inject {}public @interface Scope {}public @interface Qualifier {}

让我们逐个击破。

@Inject注解

首先最重要的DI是@Inject注解。作为JSR-330标准的一部分,标记那些依赖注入框架提供的依赖。Dagger2中,有3种不同的方式提供依赖:

构造器注入

构造器使用@Inject

public class LoginActivityPresenter {    private LoginActivity loginActivity;    private UserDataStore userDataStore;    private UserManager userManager;    @Inject    public LoginActivityPresenter(LoginActivity loginActivity,      UserDataStore userDataStore,      UserManager userManager) {        this.loginActivity = loginActivity;        this.userDataStore = userDataStore;        this.userManager = userManager;    }}

所有参数都从依赖图中获取。@Inject注解用在构造器还使得这个类成为依赖图的一部分。这意味它可以也随时注入,例如:

public class LoginActivity extends BaseActivity {@InjectLoginActivityPresenter presenter;//...}

本例中的限制是,我们只能对类中的一个构造器使用@Inject注解.

域注入

另一个方法是对特殊域使用@Inject注解:

public class SplashActivity extends AppCompatActivity {    @Inject    LoginActivityPresenter presenter;    @Inject    AnalyticsManager analyticsManager;    @Override    protected void onCreate(Bundle bundle) {        super.onCreate(bundle);        getAppComponent().inject(this);    }}

本例中注入过程称为“手工”,方式如下:

public class SplashActivity extends AppCompatActivity {    //...    @Override     protected void onCreate(Bundle bundle) {        super.onCreate(bundle);        getAppComponent().inject(this);    //Requested depenencies are injected in this moment    }}

该调用之前,我们的依赖值为null。

域注入的限制是域不能声明为private。为什么?简而言之,生成的代码会明确的调用它们,像这样:

//This class is generated automatically by Dagger 2public final class SplashActivity_MembersInjector implements MembersInjector<SplashActivity> {    //...    @Override    public void injectMembers(SplashActivity splashActivity) {        if (splashActivity == null) {            throw new NullPointerException("Cannot inject members into a null reference");        }        supertypeInjector.injectMembers(splashActivity);        splashActivity.presenter = presenterProvider.get();        splashActivity.analyticsManager = analyticsManagerProvider.get();    }}
方法注入

最后一个使用@Inject提供依赖的方式是注解类中的公共方法:

public class LoginActivityPresenter {    private LoginActivity loginActivity;    @Inject     public LoginActivityPresenter(LoginActivity loginActivity) {        this.loginActivity = loginActivity;    }    @Inject    public void enableWatches(Watches watches) {        watches.register(this);    //Watches instance required fully constructed LoginActivityPresenter    }}

所有方法参数由依赖图提供。但是为什么我们需要方法注入呢?当我们需要传类实例自身(this引用)来注入到依赖时,我们需要提供函数注入。函数注入在构造器调用后立即调用,因此这意味着我们需要传递完全构造的this

@Module注解

@Module是Dagger2 API的一部分。这个注解用于标记提供依赖的类 - 感谢这个注解,Dagger就知道在那里需要构造对象了。

@Modulepublic class GithubApiModule {    @Provides    @Singleton    OkHttpClient provideOkHttpClient() {        OkHttpClient okHttpClient = new OkHttpClient();        okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);        okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);        return okHttpClient;    }    @Provides    @Singleton    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {        RestAdapter.Builder builder = new RestAdapter.Builder();        builder.setClient(new OkClient(okHttpClient))               .setEndpoint(application.getString(R.string.endpoint));        return builder.build();    }}

@Provides注解

这个注解用在@Module类。@Provides标记Module中返回依赖的方法。

@Modulepublic class GithubApiModule {    //...    @Provides   //This annotation means that method below provides dependency    @Singleton    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {        RestAdapter.Builder builder = new RestAdapter.Builder();        builder.setClient(new OkClient(okHttpClient))               .setEndpoint(application.getString(R.string.endpoint));        return builder.build();    }}

@Component注解

这个注解用于构建接口,该接口把所有封装在一起。这里,我们定义需要依赖的模块(或组件)。这里定义了那些图依赖应当公开可见(可注入),我们的组件可以注入哪里。@Component是连接@Module@Inject的桥梁。

示例代码中@Component使用了2个模块,可以向GithubClientApplication注入依赖,并使其他3个依赖公共可见:

@Singleton@Component(    modules = {        AppModule.class,        GithubApiModule.class    })public interface AppComponent {    void inject(GithubClientApplication githubClientApplication);    Application getApplication();    AnalyticsManager getAnalyticsManager();    UserManager getUserManager();}

而且@Component可以依赖其他组件,可以定义生命周期(将来某期我将写作用域):

@ActivityScope@Component(          modules = SplashActivityModule.class,    dependencies = AppComponent.class)public interface SplashActivityComponent {    SplashActivity inject(SplashActivity splashActivity);    SplashActivityPresenter presenter();}

@Scope注解

@Scopepublic @interface ActivityScope {}

JSR-330标准的又一部分。在Dagger2中,@Scope用来实现自定义作用域注解。总之,它使得依赖非常类似与单例。注解的依赖是单例的,但是也与组件的声明周期有关(不是整个应用)。但是如前所述 - 我们将在下一篇里深入研究作用域。现在,值得提醒的是所有自定义作用域行为是一样的(从代码角度) - 他们保持单一对象实例。但是它们也用在图形校验过程,该过程有助于尽快捕获依赖图的结构问题。

现在是一些不重要,不常使用的注解:

@MapKey

这个注解用于定义依赖集合(映射和集)。例子是自解释的:

定义

@MapKey(unwrapValue = true)@interface TestKey {    String value();}

提供依赖

@Provides(type = Type.MAP)@TestKey("foo")String provideFooKey() {    return "foo value";}@Provides(type = Type.MAP)@TestKey("bar")String provideBarKey() {    return "bar value";}

使用

@InjectMap<String, String> map;map.toString() // => „{foo=foo value, bar=bar value}”

@MapKey注解现在只支持两种类型的键值 - String和Enum。

@Qualifier

@Qualifier注解帮助我们创建依赖的”tag”,依赖有同样的接口。想想一下,你需要提供两个RestAdapter
对象 - 一个用于Github API,零一二用于facebook API。 Qualifier会帮助你识别它们:

命名依赖

@Provides@Singleton@GithubRestAdapter  //QualifierRestAdapter provideRestAdapter() {    return new RestAdapter.Builder()        .setEndpoint("https://api.github.com")        .build();}@Provides@Singleton@FacebookRestAdapter  //QualifierRestAdapter provideRestAdapter() {    return new RestAdapter.Builder()        .setEndpoint("https://api.facebook.com")        .build();}

注入依赖

@Inject@GithubRestAdapterRestAdapter githubRestAdapter;@Inject@FacebookRestAdapterRestAdapter facebookRestAdapter;

全部介绍完毕。我们已经了解了Dagger 2 API的所有重要组件。

App实例

现在,是时候上手练练了。我们将实现简单的Github客户端应用,该应用基于Dagger2构建。

想法

我们的Github客户端有3个页面和非经简单的用户用例。流程如下:

  1. 输入Github用户名
  2. 如果存在就显示所有的公开仓库列表
  3. 点击列表条目,显示仓库详情

应用样子如下:

从DI的角度,我们的架构如下:

总之 - 每个activity有自己的依赖图。每个图(_Component类)有两个对象 - _Presenter_Activity。而且每个对象从全局组件得到依赖 - AppComponent,它包含其他组件ApplicationUserManagerRepositoriesManager等。

说道AppComponent,只要仔细观察这个接口。他包含两个模块:AppModule
GithubApiModule
GithubApiModule提供如OkHttpClientRestAdapter的依赖,这些依赖值用在这个模块中的其他依赖。在Dagger2中,我们可以控制哪些对象在组件外可见。在我们的例子中,我们不想暴露提到的对象。相反我们只暴露了UserManagerRepositoriesManager,因为只有这些对象用在我们的Activities中。所有这些通过返回值为非空类型且无参的公共方法定义。

文档中的例子:

预设方法

SomeType getSomeType();Set<SomeType> getSomeTypes();@PortNumber int getPortNumber();

并且我们也定义了我们想注入的依赖(通过方法注入)。例子里,AppComponent未注入到任何地方,因为它只用作我们作用域组件的一个依赖。并且每个都定义了inject(_Activity activity)方法。这里我们有简单的规则 - 使用方法定义注入,该方法只有一个参数(注入到我们的依赖中),名字无所谓,但必须返回void或者传单参数类型。

文档中的例子:

成员注入方法

SomeType getSomeType();Provider<SomeType> getSomeTypeProvider();Lazy<SomeType> getLazySomeType();

实现

我不会涉及过多的点。相反只需要克隆GithubClient代码,并在最新的Android Studio中引入即可。这里有一些要点:

Dagger2安装

仅需检查/app/build.gradle文件。我们所需做的是增加Dagger2依赖并使用android-apt插件来连接Android Studio IDE和生成的代码。

AppComponent

GithubClientApplication类开始探索GithubClient工程。这里创建并保存了AppComponent文件。这意味着所有单例对象将与Applicaiton对象(所有时期内)同生共死。

AppComponent实现由Dagger 2生成的代码提供(使用构建模式创建对象:Dagger{ComponentName}.builder())。而且这有集合了所有组件的依赖(模块和其他组件)。

区域组件

为了检查Activities组件如何创建的,只需要从SplashActivity开始查看。它重写了setupActivityComponent(AppComponent),那里它创建了自己的组件SplashActivityComponent,并注入到所有@Inject
注解的依赖(本例是SplashActivityPresenterAnalyticsManager)。

这里我们提供了AppComponent实例(因为SplashActivityPresenter依赖它),还有SplashActivityModule(提供Presenter和Acitivyt实例)。

剩下的就靠你了。认真的——揣摩一切如何结合在一起。而在接下来的文章中我们会尽量密切关注Dagger2(它是如何工作的)。

##工程的全部源码在这里。

###作者
Azimo移动部门总监

如果你喜欢这个文章,你可以分享给你的伙伴或者Twitter上关注我。

写于2015,6月10日。

0 0
原创粉丝点击