【问题标题】:Using Dagger for dependency injection on constructors使用 Dagger 对构造函数进行依赖注入
【发布时间】:2013-04-16 14:45:53
【问题描述】:

所以,我目前正在重新设计我的 Android 应用程序以使用 Dagger。我的应用又大又复杂,最近遇到了以下场景:

对象 A 需要一个特殊的 DebugLogger 实例,它是注入的完美候选者。我可以通过 A 的构造函数注入它,而不是传递记录器。这看起来像这样:

class A
{
    private DebugLogger logger;

    @Inject
    public A(DebugLogger logger)
    {
        this.logger = logger;
    }

    // Additional methods of A follow, etc.
}

到目前为止,这是有道理的。但是,A需要由另一个类B来构造。A的多个实例必须被构造,所以按照Dagger的做法,我简单地将Provider<A>注入到B中:

class B
{
    private Provider<A> aFactory;

    @Inject
    public B(Provider<A> aFactory)
    {
        this.aFactory = aFactory;
    }
}

好的,到目前为止很好。但是等等,突然 A 需要额外的输入,例如一个称为“数量”的整数,这对其构造至关重要。现在,我的 A 构造函数需要如下所示:

@Inject
public A(DebugLogger logger, int amount)
{
...
}

这个新参数突然干扰了注入。此外,即使这确实有效,我也无法在从提供者检索新实例时传递“金额”,除非我弄错了。我可以在这里做几件事,我的问题是哪一个是最好的?

我可以通过添加一个 setAmount() 方法来重构 A,该方法预计将在构造函数之后调用。然而,这很丑陋,因为它迫使我延迟 A 的构建,直到“数量”被填写。如果我有两个这样的参数,“数量”和“频率”,那么我将有两个设置器,这意味着要么复杂的检查以确保在调用两个 setter 后 A 的构造恢复,否则我将不得不添加第三种方法,如下所示:

(Somewhere in B):

A inst = aFactory.get();
inst.setAmount(5);
inst.setFrequency(7);
inst.doConstructionThatRequiresAmountAndFrequency();

另一种选择是我不使用基于构造函数的注入,而是使用基于字段的注入。但是现在,我必须公开我的领域。这不适合我,因为现在我有义务将我的班级的内部数据透露给其他班级。

到目前为止,我能想到的唯一有点优雅的解决方案是为提供程序使用基于字段的注入,如下所示:

class A
{
    @Inject
    public Provider<DebugLogger> loggerProvider;
    private DebugLogger logger;

    public A(int amount, int frequency)
    {
        logger = loggerProvider.get();
        // Do fancy things with amount and frequency here
        ...
    }
}

即使如此,我也不确定时机,因为我不确定 Dagger 是否会在调用我的构造函数之前注入提供程序。

有没有更好的方法?我只是错过了 Dagger 的工作原理吗?

【问题讨论】:

    标签: android dependency-injection dagger


    【解决方案1】:

    您所说的称为辅助注射,目前 Dagger 不以任何自动方式支持。

    您可以使用工厂模式解决此问题:

    class AFactory {
      @Inject DebugLogger debuggLogger;
    
      public A create(int amount, int frequency) {
        return new A(debuggLogger, amount);
      }
    }
    

    现在你可以注入这个工厂并使用它来创建A的实例:

    class B {
      @Inject AFactory aFactory;
    
      //...
    }
    

    当您需要使用“数量”和“频率”创建 A 时,您可以使用工厂。

    A a = aFactory.create(amount, frequency);
    

    这允许A 拥有记录器、数量和频率字段的final 实例,同时仍使用注入来提供记录器实例。

    Guice 有一个辅助注入插件,它基本上可以自动为您创建这些工厂。 Dagger 邮件列表中有have been discussion 说明添加它们的适当方式,但在撰写本文时尚未决定任何内容。

    【讨论】:

    • 感谢您的快速回复。您描述的工厂模式似乎是最好的方法。我也意识到,如果 Dagger 支持私有字段注入,它很容易实现我想要完成的任务。我想知道为什么这不是 Dagger 最初设计的一部分?我假设 Dagger 使用反射进行注入。如果是这样,那么私有字段应该没有问题,对吧?
    • Dagger 回退到反射,但这不是它的主要注入方式。它生成直接设置字段或调用构造函数的代码。因此,它与源代码树中的任何其他代码一样工作,无法访问private 成员。
    • 一些安全管理器会破坏依赖于更改类中可访问性的反射。
    • 幸运的是,我从来没有处理过任何关于使用反射的传闻问题。我听说过邪恶的安全经理。对于Android,这似乎不是问题。我的应用程序每天有大约 12 万用户进行崩溃报告,我还没有目睹任何用户手机上的安全管理器阻止反射(如果发生这种情况,我的应用程序将毫无用处,因为它执行重要的 REST/ JSON 通过反射反序列化)。但我可以看到生成的代码更加简洁,并提供编译时检查。
    • 我相当肯定 Dagger 是因为使用反射(以及其他原因)导致性能下降而编写的。 Dagger 在编译过程中会展示它的肌肉,并(尽可能)生成代码以避免反射。
    【解决方案2】:

    Jake 的帖子所说的完全正确。也就是说,我们(一些使用 Guice 和 Dagger 的 Google 人员)正在研究“辅助注入”或自动工厂生成的替代版本,它应该可由 Guice 或 Dagger 使用或独立使用 - 也就是说,它将为您生成工厂类源代码。这些工厂类(如果合适)将像任何标准 JSR-330 类一样可注入。但它还没有发布。

    等待这样的解决方案,Jake Wharton 的方法是可取的。

    【讨论】:

    • 是的,不用担心,我意识到 Dagger 仍处于早期版本。我确实喜欢将其中一些其他功能作为插件开发,以保持二进制文件的整体大小(至少对我而言,这使它成为 Android 开发如此有吸引力的选择)。
    • 所以提到了自动工厂生成:github.com/google/auto/tree/master/factory
    【解决方案3】:

    您遇到了问题,因为您在构造函数中混合了可注射剂和不可注射剂。注射的一般规则可以为您节省大量的心痛并保持您的代码干净:

    1. Injectables 可以在它们的构造函数中请求其他的 injectables,但不能请求 newables。

    2. Newables 可以在其构造函数中请求其他 newables,但不能请求可注射物。

    Injectables 是服务类型的对象,即可以工作的对象,例如 CreditCardProcessor、MusicPlayer 等。

    Newables 是值类型的对象,例如 CreditCard、Song 等。

    【讨论】:

      【解决方案4】:

      Jake 的帖子很棒,但还有更简单的方法。 Google 创建了AutoFactory 库,用于在编译时自动创建工厂。

      首先,使用@AutoFactory 注释和@Provided 注释创建类A 用于注入参数:

      @AutoFactory
      public class A {
      
          private DebugLogger logger;
      
          public A(@Provided DebugLogger logger, int amount, int frequency) {
              this.logger = logger;
          }
      }
      

      然后库在编译时创建AFactory 类。因此,您只需将工厂注入B 类的构造函数即可。

      public class B {
      
          private final AFactory aFactory;
      
          @Inject
          public B(AFactory aFactory) {
              this.aFactory = aFactory;
          }
      
          public A createA(int amount, int frequency) {
              return aFactory.create(amount, frequency);
          }
      }
      

      【讨论】:

        【解决方案5】:

        我只是想补充一下,在这个问题发布后几年过去了,现在有一个名为的库 AssistedInject 由 Jake 和 Square 的朋友创建, 解决完全相同的问题,并且与 Dagger 2 完全兼容。

        你可以在这里找到它:https://github.com/square/AssistedInject

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-02-02
          • 1970-01-01
          • 1970-01-01
          • 2017-01-29
          • 1970-01-01
          • 1970-01-01
          • 2019-04-20
          相关资源
          最近更新 更多