Dagger 2 注入构造函数

Dagger 2 Injecting Constructors

我开始在我正在开发的应用程序中使用 Dagger 2,但我对 Dagger 2 的工作原理有一些疑问。

我了解了 @Provides 方法和用于初始化依赖项的 @Inject 注释背后的所有逻辑,但是 class 构造函数的 @Inject 注释让我心烦意乱。

例如:

在我的应用程序中,我定义了一个模块 ContextModule,用于检索我的应用程序的上下文:

ContextModule.java

@Module
public class ContextModule {

    private final Context context;

    public ContextModule(Context context) {
        this.context = context;
    }

    @Provides
    public Context context() {
        return this.context;
    }
}

这个模块被我的 BaseActivityComponent 使用:

BaseActivityComponent.java

@BaseActivityScope
@Component(modules = ContextModule.class)
public interface BaseActivityComponent {
    void injectBaseActivity(BaseActivity baseActivity);
}

到目前为止一切顺利..然后我有一个 AuthController class,这取决于上下文,我想将它注入我的 BaseActivity。所以在我的 AuthControllers.class 中我有类似的东西:

public class AuthController {

    private Context context;

    @Inject
    public AuthController(Context context) {
        this.context = context;
    }

    public void auth() {
        // DO STUFF WITH CONTEXT
    }
}

然后我将它注入到我的 BaseActivity 中,例如:

public class BaseActivity extends AppCompatActivity {

    @Inject
    AuthController authController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        BaseActivityComponent component = DaggerBaseActivityComponent.builder()
            .contextModule(new ContextModule(this))
            .build();

        component.injectBaseActivity(this);

        authController.auth();

    }
}

现在我的问题是,dagger 如何知道我的 AuthControllers 是 BaseActivity 的依赖项?只需声明

@Inject
AuthController authController;

这就像我创建一个 ControllerModule 一样:

@Module(includes = ContextModule.class)
public class ControllerModule {

    @Provides
    AuthController authController(Context context) {
        return new AuthController(context);
    }

}

然后在我的 BaseActivityComponent 中添加我的 AuthController getter 并将我的依赖模块更改为 ControllersModule:

@BaseActivityScope
@Component(modules = ControllersModule.class)
public interface BaseActivityComponent {

    void injectBaseActivity(BaseActivity baseActivity);

    AuthController getAuthController();
}

当我调用 injectBaseActivity(this) 时,它 "tells" 怀疑所有 @Inject 注释都是我的 class 的依赖项,然后它会在我的项目中搜索与该类型匹配的 @Inject 注释构造函数?

我认为 Dagger 2 的一个好处是模块文件可以用作我的依赖项 3 的 "documentation"。但是,如果只是在我控制的所有构造函数中添加 @Inject,将来会不会有点混乱,因为您不知道什么实际上取决于什么? (我的意思是,你知道什么取决于什么,你只需要浏览大量文件才能真正找出答案)

在构造函数中使用@Inject 注释或在模块文件中添加@Provides 方法时,是否有任何最佳实践? 我知道在构造函数中使用 @Inject 我不需要更改我的模块文件中的构造函数定义,但是有什么缺点吗?

谢谢。

When I call injectBaseActivity(this) it "tells" dagger that all @Inject annotations are dependencies of my class, and then it searches my project for @Inject annotated constructors that matches that type?

没错。但是当你调用 injectBaseActivity 时它并没有完成,但这一切都发生在编译期间。这是注释处理的一种方式(另一种在运行时使用反射)。

当您构建项目时,您在 build.gradle 文件中包含(作为依赖项)的 dagger-annotation-processor 将被调用,其中包含所有字段的列表,classes 等注释通过 @Inject 注释并用它构建一个依赖图。然后解析图表,生成源代码,为图表上的项目提供所有依赖关系。

injectBaseActivity 只是执行之前生成的代码,并将所有依赖项分配给您的对象。这是正确的源代码,您可以阅读和调试。

这是一个编译步骤的原因——简而言之——是为了性能和验证。 (例如,如果你有一些依赖循环,你会得到一个编译错误)


how does dagger knows that my AuthControllers is a dependency for BaseActivity?

@Inject
AuthController authController;

通过注释字段 @Inject,dagger 知道您想要一个 AuthController。到目前为止,一切都很好。现在 dagger 将寻找一些方法来提供控制器,在组件、组件依赖项和组件模块中寻找它。它还会查看 class 是否可以自己提供,因为它 知道 其构造函数。

如果您不将它包含在任何模块中,dagger 如何知道对象构造函数?

@Inject
public AuthController(Context context) { /**/ }

通过使用 inject 注释构造函数,您还告诉 dagger 有一个名为 AuthController 的 class,您需要一个上下文来实例化它。它与将它添加到您的模块基本相同。

如果您没有源代码只能将 @Inject 注释添加到构造函数,或者如果对象需要进一步初始化,则应使用模块 @Provides 方法。或者在你的情况下......

[...]the Module files could be used as a "documentation" of my dependencies tree [...]

是的,你当然可以这样做。但是随着项目的增长,您将不得不维护 大量 不必要的代码,因为同样可以通过构造函数上的简单注释来完成。

Is there any best practices for when using @Inject annotations in constructors or when to add the @Provides method in Modules files?

如果您想为不同的上下文提供不同的版本(例如,以两种不同的方式实现接口),还有 @Binds 注释告诉 dagger 您希望提供哪个 class 作为实施。

除此之外,我相信您应该尽可能使用构造函数注入。如果有什么变化,您不必触及代码的任何其他部分,而且您编写的代码会更少,因此您可能包含错误的地方也会更少。

Dagger 也可以并且确实通过了解更多来进行很多优化,如果您实现不必要的代码,它将不得不使用您引入的开销


当然最后还是看你觉得怎样最好。毕竟 必须使用 你的 代码 ;)