Dagger 2:将用户输入的参数注入对象

73

假设我有一个名为Util的类,它接受一个对象——Validator类的实例。

由于我想避免在Util内实例化Validator类,所以我通过构造函数将其传递进去:

public class Util {

   @Inject
   public Util(Validator validator) {

   }


}

我有一个模块,提供了验证器实例:

@Provides
@Singleton
Validator provideValidator() {
    return Validator.getInstance();
}

以及 Util 类的一个实例:

@Provides
Util provideUtil(Validator validator) {
    return new Util(validator);
}

我有一个已经连接好的组件,可以给我提供Util的实例:

Util getUtil()

在我的活动中,我可以这样调用它:

Util myUtil = getComponent.getUtil();

所有的内容都很好 - 当实例化时,myUtil有一个适当的Validator类实例。

现在我想传入一个名为address的字符串变量(这是通过UI输入的用户输入)。 我想要更改构造函数,以便我传递Validator的实例和用户输入的字符串:

@Inject
public Util(Validator validator, String address) {

}

我真的无法理解如何传递第二个参数。有人能告诉我吗?

理想情况下,我想这样实例化Util:

Util myUtil = getComponent.getUtil(txtAddress.getText());
5个回答

125
我在几周前开始研究Dagger 2时,也有和你一样的问题。我发现这个问题(以及其他大多数与Dagger 2相关的问题)都很难找到信息,所以我希望这可以帮到你!
最基本的答案是不行。你要找的是被称为“辅助注入”的东西,它不是Dagger 2的一部分。某些其他依赖注入(DI)框架,如Guice,提供了这个功能,因此你可以考虑使用它们。当然,仍然有方法可以使用Dagger 2实现你想做的事情。
工厂、工厂、工厂
与DI结合使用实现你想做的事情的标准方法是使用工厂模式。基本上,你创建一个可注入的工厂类,该类将运行时参数(例如address)作为其提供的对象创建方法的参数。
在您的情况下,您需要一个UtilFactory,Dagger 2会在实例化时注入一个Validator,并提供一个create(String address)方法来创建Util的实例。 UtilFactory应该保留对Validator的注入实例的引用,以便它在create方法中拥有创建Util实例所需的所有内容。
为许多这样的工厂编写代码可能很麻烦。 您应该确实看一下AutoFactory,它可以减轻一些负担。 Guice的辅助注入似乎与Dagger 2 + AutoFactory非常相似(尽管具有更好的语法糖)。

更多模块/组件

我怀疑在这种情况下您可能不想这样做,但是您可以创建一个提供地址(并实例化新组件)的模块。您不必为每个可能的地址创建新的@Module类。相反,您可以将地址作为参数传递给模块的构造函数。您可以使用@BindsInstance注释,如teano建议,以实现类似的结果。
我不确定这是否是反模式。对我来说,在某些情况下,这似乎是可接受的路线,但仅当您实际上将同一地址用于“许多”对象的初始化时才是如此。您绝对不想为需要注入的每个对象都实例化新的组件和新的模型。这不是高效的,如果您不小心,您最终会比没有Dagger时拥有更多的样板代码。
不要(总是)使用DI:Injectables versus newables

在学习依赖注入(DI)框架时,对我非常有用的一点是意识到使用DI框架并不意味着您必须DI来初始化所有对象。作为一个经验法则:注入您在编译时知道并且与其他对象具有静态关系的对象; 不要注入运行时信息。

我认为this是这个主题的好文章。它介绍了“可注入”和“新建”概念。

  • 可注入是DI图中靠近根部的类。这些类的实例是您期望DI框架提供和注入的对象类型。管理器或服务类型的对象是典型的可注入示例。
  • 新建是DI图边缘或根本不是DI图的对象。整数,地址等是新建的示例。
总的来说,Newables 是被动对象,注入或模拟它们是没有意义的。它们通常包含应用程序中仅在运行时可用的“数据”(例如您的地址)。Newables 不应保留对 injectables 的引用,反之亦然(这是作者所称的“injectable/newable-separation”)。
实际上,我发现清楚地区分 injectables 和 newables 并不总是容易或可能的。尽管如此,我认为它们是作为思考过程的一部分使用的好概念。在向项目添加另一个工厂之前,请三思而后行!
在您的情况下,我认为将 Util 视为 injectable,将地址视为 newable 是有意义的。这意味着地址不应是 Util 类的一部分。如果您想使用 Util 的实例进行验证/... 地址等操作,请将要验证的地址作为参数传递给验证/... 方法。
2021 年更新

自Dagger 2.31版本以来,还有一种使用@AssistedInject进行辅助注入的机制。您可以在此处文档中了解更多信息(在Jay Sidri的建议下进行编辑)。


4
需要强调最后一个观点,并且我很喜欢“新能源”的说法,我得借用一下。 - Jeff Bowman
关于第一个解决方案:您如何将验证器依赖项引入UtilFactory中? UtilFactory是否会成为所需Util实例的“可注入”/依赖项,并通过DI框架在程序启动/初始化时“连接在一起”(在这种情况下获取验证器依赖项)?当工厂散布在整个程序中时,这不是很奇怪吗? - cobby
或者换句话说:有没有办法在使用工厂的类中不将工厂作为依赖项添加? - cobby
1
不知道您的应用程序具体情况,但我认为是的,您需要将依赖项注入工厂对象。可以通过注入工厂来实现。通过创建一个工厂类,您可以将所有可以/应该注入的内容与您想要在运行时添加的内容分开:对“可注入”依赖项进行处理并放入工厂类中,对“新建”依赖项则作为参数传递给工厂方法。某个Util所需的特定依赖关系并不会改变。如果您认为类的依赖关系过多,那可能意味着它们耦合过紧。 - Semafoor
@Semafoor 感谢您的澄清!看起来在Dagger中使用工厂就像Spring的BeanFactory#getBean(String, Object...)一样是解决运行时依赖的解决方案? - cobby
显示剩余7条评论

20

您可以更改组件生成器以注入实例。请参见:https://google.github.io/dagger/users-guide#binding-instances

在您的情况下,您可以调用:

Util myUtil = DaggerMyComponent.builder().withAddress(txtAddress.getText()).build().getComponent().getUtil();

如果MyComponent被定义为:

@Component(modules = UtilModule.class)
interface MyComponent{

    MyComponent getComponent();

    @Component.Builder
    interface Builder {

        @BindsInstance Builder withAddress(@Address String address); //bind address instance

        MyComponent build();

    }
}

同时也有UtilModule模块:

@Module
class UtilModule{

    @Provides
    Util getUtil(Validator validator, @Address String address){ //inject address instance
        return new Util(validator, address);
    }

}

Validator必须在Component注解的MyComponent模块中提供具有@Inject注释的构造函数或@Provides注释的方法。

更新:

@Address是一个可以定义为Qualifier的注释。

@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Qualifier
public @interface Address {}

也可以使用@Named限定符替换,例如@Named("Address")。

有关限定符的更多信息请参见Dagger指南


@Address 是什么? - Anup Ammanavar
已经在上面的回答中回答了。 - arungiri_10
“在上面的答案中回答”是什么意思?答案会移动。请解释@Address的原始含义。是否需要先创建它?它只是某个地方声明的注释,还是有什么独特之处? - Matt Wolfe
是的,它是一个在 https://dagger.dev/dev-guide 上的 Qualifiers 部分中声明的注解。 - teano
应该在接口MyComponent中将"MyComponent getComponent();"更改为"Util util();",并且调用逻辑是:"Util myUtil = DaggerMyComponent.builder().withAddress(...).build().util();"。 - cn123h

3

当初始化模块时,您可以像这样传递一些参数:

public NetServiceModule(String baseUrl, boolean isLogEnabled, CookieJar cookieJar) {
    this.mBaseUrl = baseUrl;
    this.mIsLogEnabled = isLogEnabled;
    this.mCookieJar = cookieJar;
}

然后在“容器类”中获取组件:

NetServiceComponent component = DaggerNetServiceComponent.builder()
            .netServiceModule(new NetServiceModule(baseUrl, mIsLogEnabled, cookieJar))
            .build();
    component.inject(this);

使用Provides方法提供注入,如果需要生成一些参数,则使用这些参数:
@Provides
Retrofit provideRetrofit(OkHttpClient httpClient, GsonConverterFactory gsonConverterFactory, NetCallAdapterFactory netCallAdapterFactory) {

    return new Retrofit.Builder()
            .client(httpClient)
            .baseUrl(mBaseUrl)
            .addConverterFactory(gsonConverterFactory)
            .addCallAdapterFactory(netCallAdapterFactory)
            .build();
}

2
自从Dagger 2的2.31版本,就有了一种辅助注入机制。我们可以使用@AssistedInject来实现。您可以在这里的文档中了解更多信息。
示例:
我们有一个需要辅助注入的类:
class MyDataService {
  @AssistedInject
  MyDataService(DataFetcher dataFetcher, @Assisted Config config) {}
}

我们使用@AssistedInject来表示我们的构造函数需要辅助注入,而使用@Assisted来标记哪些参数将被辅助注入。
然后,我们需要为辅助注入定义一个工厂。
@AssistedFactory
public interface MyDataServiceFactory {
  MyDataService create(Config config);
}

最后,Dagger将为我们生成这个工厂,我们将能够在需要使用我们的依赖项的地方注入它。
class MyApp {
  @Inject MyDataServiceFactory serviceFactory;

  MyDataService setupService(Config config) {
    MyDataService service = serviceFactory.create(config);
    // ...
    return service;
  }
}

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。- 来自审查 - east1000

0
@Inject
Usermodel uuser;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    userComponent dc = DaggeruserComponent.create();
    dc.injectMain(this);

    historymodel hm =  uuser.getHistorymodel();// get the models to pass user inputs 

    videoModel vm = uuser.getVideoModel();//  get the models to pass user inputs

    hm.setUid("userid ");


}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接