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

Vij*_*ede 5 java android dependency-injection dagger-2

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

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

例:

我有一个界面.

Product.java

public interface Product {}
Run Code Online (Sandbox Code Playgroud)

ProductOne和ProductTwo共有两个具体的类.

ProductOne.class

public class ProductOne implements Product{

@Inject
public ProductOne() {}

}
Run Code Online (Sandbox Code Playgroud)

包装类是客户端.

Packaging.java

public class Packaging{

@Inject
public Packaging(Product product){}

}
Run Code Online (Sandbox Code Playgroud)

直到这一刻,我的包类使用了ProductOne类的实例.

混乱:

如果我有另一个具有@Inject批注的具体类ProductTwo.

public class ProductTwo implements Product {

@Inject
public ProductTwo() {}

}
Run Code Online (Sandbox Code Playgroud)

现在在我的Packaging类中,我想使用ProductTwo类的实例,那么这个@Inject注释会在这个时候起作用吗?

Vij*_*ede 6

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

对于上面的例子,在我们的 Dagger Packaging 模块中,我们必须提供ProductOne依赖ProductTwo项。

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


@Provides @Named("product two") Product provideProductTwo() {
    return new ProductTwo();
} 
Run Code Online (Sandbox Code Playgroud)

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

public class Packaging{

Product product;

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

}
Run Code Online (Sandbox Code Playgroud)

如果我们需要 ProductTwo 的实例。

public class Packaging{

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

}
Run Code Online (Sandbox Code Playgroud)

@Named注释只不过是使用@Qualifier包含在javax.inject

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}
Run Code Online (Sandbox Code Playgroud)

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


Pau*_*fin 4

我假设,由于您没有提及模块或组件,因此您对它们以及它们如何协同工作不太熟悉。

您的示例将不起作用,因为 Dagger 2 不知道为了生成 Product 它需要使用 ProductOne 或 ProductTwo 类之一。即使 Dagger 2 会处理它们(因为它们都被标记为 @Inject),它也不会自动假设仅仅因为它们实现了 Product 就应该在那里使用它们。其原因是,当有多个(如本例所示)时,它不知道该使用哪一个。

因此,您必须使用 Product 接口从 ProductOne 或 ProductTwo 创建绑定。您可以通过模块来完成此操作。

@Module
public class ProductOneModule {
  @Provides Product provideProduct(ProductOne productOne) {
    return productOne;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

@Component
public interface PackagerOneComponent {
  Packager packager();
}
Run Code Online (Sandbox Code Playgroud)

错误将是这样的:

如果没有 @Provides 注释的方法,则无法提供产品。
    包装机.(产品产品)
    【参数:产品产品】

这意味着当尝试创建Packager对象时,它无法为其Product参数找到合适的绑定。解决这个问题的方法是指定模块及其绑定 from Product < ProductOne

@Component(modules = ProductOneModule.class)
public interface PackagerOneComponent {
  Packager packager();
}
Run Code Online (Sandbox Code Playgroud)

现在它知道要创建一个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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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