@Inject 注释如何知道在同一接口下实例化哪个具体 class?

How @Inject annotation would know which concrete class to instantiate under same interface?

我在 Android 应用程序中使用 Dagger2.0

我对 @Inject 注释感到困惑。我有两个实现相同接口的具体 class。 我正在使用 @Inject 注释注入具体 class 之一。 这里,@Inject 注释如何决定要实例化哪个具体 class。

示例:

我只有一个接口。

Product.java

public interface Product {}

总共有两个具体的 classes ProductOne 和 ProductTwo。

ProductOne.class

public class ProductOne implements Product{

@Inject
public ProductOne() {}

}

打包class是客户端

Packaging.java

public class Packaging{

@Inject
public Packaging(Product product){}

}

到目前为止,我的包 class 使用 ProductOne class 的实例。

困惑:

如果我有另一个具体的 class ProductTwo 带有 @Inject 注释。

public class ProductTwo implements Product {

@Inject
public ProductTwo() {}

}

现在在我的包装中 class 我想使用 ProductTwo 的实例 class,那么这个 @Inject 注释此时可以工作吗?

这个例子将不起作用。对于这种情况,我们必须使用 @Named 注释。

对于上面的示例,在我们的 Dagger 打包模块中,我们必须提供 ProductOneProductTwo 依赖项。

@Provides @Named("product one") Product provideProductOne() {
    return new ProductOne();
}


@Provides @Named("product two") Product provideProductTwo() {
    return new ProductTwo();
} 

现在当我们需要注入这个依赖时,我们可以通过以下方式注入它。

public class Packaging{

Product product;

@Inject
public Packaging(@Named("product one") Product product){
    this.product = product;
}

}

如果我们需要 ProductTwo 的实例。

public class Packaging{

Product product;
@Inject
public Packaging(@Named("product two")Product product){
    this.product = product;
}

}

@Named 注释只是使用 javax.inject

中包含的 @Qualifier 注释
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

我们不必提供此声明,因此 Dagger 会为我们做这件事。

我假设您没有提到任何模块或组件,您对它们以及它们如何协同工作不是很熟悉。

您的示例将无法运行,因为 Dagger 2 不知道要生成产品,它需要使用 ProductOne 或 ProductTwo 之一 类。尽管 Dagger 2 会处理它们(因为它们都被标记为@Inject),但它不会自动假定仅仅因为它们实现了 Product 就应该在那里使用它们。原因是在这种情况下,当有多个时,它不知道该使用哪个。

因此,您必须使用 Product 接口从 ProductOne 或 ProductTwo 创建绑定。你通过一个模块来做到这一点。

@Module
public class ProductOneModule {
  @Provides Product provideProduct(ProductOne productOne) {
    return productOne;
  }
}

一个模块只是提供一组可重用的绑定。除非它们被组件使用,否则它们不会被实际使用(或验证)。组件是封装所有这些信息并管理它们的创建的东西,使用为 类s 和 @Inject 构造函数创建的模块及其绑定和工厂。

如果您创建这样的组件,那么 dagger 2 将失败,因为如上所述它不知道如何生成产品。

@Component
public interface PackagerOneComponent {
  Packager packager();
}

错误将是这样的:

Product cannot be provided without an @Provides-annotated method.
    Packager.(Product product)
    [parameter: Product product]

这意味着当试图创建一个 Packager 对象时,它无法为其 Product 参数找到合适的绑定。解决此问题的方法是使用 Product < ProductOne.

中的绑定来指定模块
@Component(modules = ProductOneModule.class)
public interface PackagerOneComponent {
  Packager packager();
}

现在它知道要创建一个 Product 它需要调用 ProductOneModule.provideProduct(ProductOne) 并且为了调用它需要创建一个 ProductOne 它知道该怎么做因为你用 @Inject.

标记了它的构造函数之一

当然,如果您想使用 ProductTwo,那么您可以创建另一个模块和组件。

@Module
public class ProductTwoModule {
  @Provides Product provideProduct(ProductTwo productTwo) {
    return productTwo;
  }
}

@Component(modules = ProductTwoModule.class)
public interface PackagerTwoComponent {
  Packager packager();
}

在这种情况下使用限定符(自定义限定符或命名限定符)的问题在于,使用限定符时,您会将注入点与特定实现紧密耦合。在某些情况下这绝对是必需的,例如如果您有两个 Long 实例,其中一个是超时,一个是端口,您不希望它们混淆,因此您肯定需要使用限定符来区分它们。

但是,在这种情况下,某些用户或包装可能希望使用 ProductOne,而另一些则希望使用 ProductTwo。否则,Packager 应该直接使用 ProductOne 或 ProductTwo 并避开接口。

这种方法允许您的代码的两个不同部分将 Packager 与 Product 的两个不同实现一起使用,例如您的生产和测试。

当然,您可以使用两种不同的实现,即使它是用限定符注释的,但您仍然必须使用多种这种技术。