Dagger 2:将用户输入的参数注入对象
Dagger 2: Injecting user inputted parameter into object
假设我有一个 class Util 接受一个对象 - class Validator 的一个实例.
因为我想避免在 Util 中实例化验证器 class,所以我通过构造函数将其传入:
public class Util {
@Inject
public Util(Validator validator) {
}
}
我有一个提供验证器实例的模块:
@Provides
@Singleton
Validator provideValidator() {
return Validator.getInstance();
}
和一个 Util 实例 class:
@Provides
Util provideUtil(Validator validator) {
return new Util(validator);
}
我连接了一个组件,可以给我一个 Util 实例:
Util getUtil()
所以在我的 activity 中,我可以这样称呼它:
Util myUtil = getComponent.getUtil();
所有这些工作正常 - myUtil 在实例化时有一个正确的 Validator class 实例。
现在我想传入一个名为 address 的字符串变量(用户通过 UI 输入)。我想更改构造函数,所以我传入了一个 Validator 实例和用户输入的 String:
@Inject
public Util(Validator validator, String address) {
}
我只是想不通如何传递第二个参数。有人能告诉我怎么做吗?
理想情况下,我想像这样实例化 Util:
Util myUtil = getComponent.getUtil(txtAddress.getText());
几周前开始研究 Dagger 2 时,我遇到了与您相同的问题。我发现很难获得关于此(以及大多数其他 Dagger 2 相关问题)的信息,所以我希望这对您有所帮助!
最基本的答案是你不能。您正在寻找的是称为 assisted injection 的东西,它不是 Dagger 2 的一部分。一些其他依赖注入 (DI) 框架,例如 Guice,做提供此功能,因此您可能会研究这些功能。当然,还有一些方法可以使用 Dagger 2 做您想做的事。
工厂工厂工厂
结合 DI 执行您想执行的操作的标准方法是使用工厂模式。基本上,您创建一个可注入工厂 class,它将运行时参数(例如 address
)作为其提供的对象创建方法的参数。
在您的情况下,您需要一个 UtilFactory
,Dagger 2 在实例化时将 Validator
注入其中,并提供一个创建 Util
实例的方法 create(String address)
. UtilFactory
应该保留对 Validator
注入实例的引用,以便它拥有在 create
方法中创建 Util
实例所需的一切。
许多这样的工厂的代码可能很麻烦。您绝对应该看看 AutoFactory,它可以减轻一些负担。 Guice 的辅助注入似乎与 Dagger 2 + AutoFactory 的工作方式非常相似(尽管语法糖更好)。
更多模块/组件
我怀疑您是否愿意在这种情况下执行此操作,但您可以 只创建一个提供地址的模块(并实例化一个新组件)。您不必为每个可能的地址创建一个新的@Module class。相反,您可以将地址作为参数传递给模块的构造函数。您可以按照 teano 的建议使用 @BindsInstance-annotation 来获得类似的结果。
我不确定这是否是反模式。对我来说,这在某些情况下似乎是可以接受的路线,但前提是您实际使用相同的路线,例如“许多”对象的初始化地址。你肯定不想为每个需要注入的对象实例化一个新组件和一个新模型。它效率不高,如果你不小心,你最终会得到比没有 Dagger 时更多的样板代码。
不要(总是)使用 DI:注射剂与新药
在学习 DI 框架时对我非常有用的是认识到使用 DI 框架不意味着你必须 DI 来初始化 您的所有 个对象。作为一个经验法则:注入你在编译时知道的对象,并且与其他对象有静态关系;不要注入运行时信息。
我认为 this 在这个问题上很好 post。它引入了'newables'和'injectables'的概念。
- Injectables 是靠近 DI 图根部的 classes。这些 classes 的实例是您希望 DI 框架提供和注入的对象类型。经理或服务类型的对象是可注入的典型示例。
- Newables 是 DI 图表边缘的对象,或者根本不是 DI 图表的一部分。
Integer
、Address
等是新产品的例子。
从广义上讲,newables 是被动对象,注入或嘲笑它们是没有意义的。它们通常包含您的应用程序中的“数据”,并且仅在运行时可用(例如您的地址)。 Newables 不应保留对注射剂的引用,反之亦然(post 的作者将其称为“injectable/newable-separation”)。
实际上,我发现在注射剂和新剂之间做出明确区分并不总是那么容易或不可能。不过,我认为它们是很好的概念,可以用作您思考过程的一部分。在向您的项目添加另一个工厂之前一定要三思!
在您的情况下,我认为将 Util
视为可注射物并将地址视为可更新物是有意义的。这意味着该地址不应是 Util
class 的一部分。如果您想使用 Util
的实例,例如validating/... 地址,只需将要验证的地址作为参数传递给 validation/... 方法。
2021 年更新
从2.31版本的Dagger 2开始,也有了使用@AssistedInject
进行辅助注入的机制。您可以在文档中查看更多信息 here. (Edited at the suggestion of Jay Sidri。)
启动模块时,可以传递一些参数,如:
public NetServiceModule(String baseUrl, boolean isLogEnabled, CookieJar cookieJar) {
this.mBaseUrl = baseUrl;
this.mIsLogEnabled = isLogEnabled;
this.mCookieJar = cookieJar;
}
然后获取"Container Class"中的组件:
NetServiceComponent component = DaggerNetServiceComponent.builder()
.netServiceModule(new NetServiceModule(baseUrl, mIsLogEnabled, cookieJar))
.build();
component.inject(this);
通过Provides方法提供Injection,如果需要,可以通过一些参数生成:
@Provides
Retrofit provideRetrofit(OkHttpClient httpClient, GsonConverterFactory gsonConverterFactory, NetCallAdapterFactory netCallAdapterFactory) {
return new Retrofit.Builder()
.client(httpClient)
.baseUrl(mBaseUrl)
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(netCallAdapterFactory)
.build();
}
您可以更改组件生成器以注入实例。参见: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 必须在模块中提供 @Inject 注释构造函数或 @Provides 注释方法 class 在 @Component 注释中传递给 MyComponent 的模块。
更新:
@Address 是一个 Qualifier 可以定义为:
@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Qualifier
public @interface Address {}
and 也可以替换为 @Named 限定符,例如@Named("地址").
有关限定符的详细信息,请参阅 Dagger Guide。
@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 ");
}
}
从2.31版本的Dagger 2开始,就有了辅助注入的机制。我们可以为此使用@AssistedInject。您可以在文档 here.
中查看更多信息
示例:
我们有一个 class 需要辅助注射:
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;
}
}
假设我有一个 class Util 接受一个对象 - class Validator 的一个实例.
因为我想避免在 Util 中实例化验证器 class,所以我通过构造函数将其传入:
public class Util {
@Inject
public Util(Validator validator) {
}
}
我有一个提供验证器实例的模块:
@Provides
@Singleton
Validator provideValidator() {
return Validator.getInstance();
}
和一个 Util 实例 class:
@Provides
Util provideUtil(Validator validator) {
return new Util(validator);
}
我连接了一个组件,可以给我一个 Util 实例:
Util getUtil()
所以在我的 activity 中,我可以这样称呼它:
Util myUtil = getComponent.getUtil();
所有这些工作正常 - myUtil 在实例化时有一个正确的 Validator class 实例。
现在我想传入一个名为 address 的字符串变量(用户通过 UI 输入)。我想更改构造函数,所以我传入了一个 Validator 实例和用户输入的 String:
@Inject
public Util(Validator validator, String address) {
}
我只是想不通如何传递第二个参数。有人能告诉我怎么做吗?
理想情况下,我想像这样实例化 Util:
Util myUtil = getComponent.getUtil(txtAddress.getText());
几周前开始研究 Dagger 2 时,我遇到了与您相同的问题。我发现很难获得关于此(以及大多数其他 Dagger 2 相关问题)的信息,所以我希望这对您有所帮助!
最基本的答案是你不能。您正在寻找的是称为 assisted injection 的东西,它不是 Dagger 2 的一部分。一些其他依赖注入 (DI) 框架,例如 Guice,做提供此功能,因此您可能会研究这些功能。当然,还有一些方法可以使用 Dagger 2 做您想做的事。
工厂工厂工厂
结合 DI 执行您想执行的操作的标准方法是使用工厂模式。基本上,您创建一个可注入工厂 class,它将运行时参数(例如 address
)作为其提供的对象创建方法的参数。
在您的情况下,您需要一个 UtilFactory
,Dagger 2 在实例化时将 Validator
注入其中,并提供一个创建 Util
实例的方法 create(String address)
. UtilFactory
应该保留对 Validator
注入实例的引用,以便它拥有在 create
方法中创建 Util
实例所需的一切。
许多这样的工厂的代码可能很麻烦。您绝对应该看看 AutoFactory,它可以减轻一些负担。 Guice 的辅助注入似乎与 Dagger 2 + AutoFactory 的工作方式非常相似(尽管语法糖更好)。
更多模块/组件
我怀疑您是否愿意在这种情况下执行此操作,但您可以 只创建一个提供地址的模块(并实例化一个新组件)。您不必为每个可能的地址创建一个新的@Module class。相反,您可以将地址作为参数传递给模块的构造函数。您可以按照 teano 的建议使用 @BindsInstance-annotation 来获得类似的结果。
我不确定这是否是反模式。对我来说,这在某些情况下似乎是可以接受的路线,但前提是您实际使用相同的路线,例如“许多”对象的初始化地址。你肯定不想为每个需要注入的对象实例化一个新组件和一个新模型。它效率不高,如果你不小心,你最终会得到比没有 Dagger 时更多的样板代码。
不要(总是)使用 DI:注射剂与新药
在学习 DI 框架时对我非常有用的是认识到使用 DI 框架不意味着你必须 DI 来初始化 您的所有 个对象。作为一个经验法则:注入你在编译时知道的对象,并且与其他对象有静态关系;不要注入运行时信息。
我认为 this 在这个问题上很好 post。它引入了'newables'和'injectables'的概念。
- Injectables 是靠近 DI 图根部的 classes。这些 classes 的实例是您希望 DI 框架提供和注入的对象类型。经理或服务类型的对象是可注入的典型示例。
- Newables 是 DI 图表边缘的对象,或者根本不是 DI 图表的一部分。
Integer
、Address
等是新产品的例子。
从广义上讲,newables 是被动对象,注入或嘲笑它们是没有意义的。它们通常包含您的应用程序中的“数据”,并且仅在运行时可用(例如您的地址)。 Newables 不应保留对注射剂的引用,反之亦然(post 的作者将其称为“injectable/newable-separation”)。
实际上,我发现在注射剂和新剂之间做出明确区分并不总是那么容易或不可能。不过,我认为它们是很好的概念,可以用作您思考过程的一部分。在向您的项目添加另一个工厂之前一定要三思!
在您的情况下,我认为将 Util
视为可注射物并将地址视为可更新物是有意义的。这意味着该地址不应是 Util
class 的一部分。如果您想使用 Util
的实例,例如validating/... 地址,只需将要验证的地址作为参数传递给 validation/... 方法。
2021 年更新
从2.31版本的Dagger 2开始,也有了使用@AssistedInject
进行辅助注入的机制。您可以在文档中查看更多信息 here. (Edited at the suggestion of Jay Sidri。)
启动模块时,可以传递一些参数,如:
public NetServiceModule(String baseUrl, boolean isLogEnabled, CookieJar cookieJar) {
this.mBaseUrl = baseUrl;
this.mIsLogEnabled = isLogEnabled;
this.mCookieJar = cookieJar;
}
然后获取"Container Class"中的组件:
NetServiceComponent component = DaggerNetServiceComponent.builder()
.netServiceModule(new NetServiceModule(baseUrl, mIsLogEnabled, cookieJar))
.build();
component.inject(this);
通过Provides方法提供Injection,如果需要,可以通过一些参数生成:
@Provides
Retrofit provideRetrofit(OkHttpClient httpClient, GsonConverterFactory gsonConverterFactory, NetCallAdapterFactory netCallAdapterFactory) {
return new Retrofit.Builder()
.client(httpClient)
.baseUrl(mBaseUrl)
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(netCallAdapterFactory)
.build();
}
您可以更改组件生成器以注入实例。参见: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 必须在模块中提供 @Inject 注释构造函数或 @Provides 注释方法 class 在 @Component 注释中传递给 MyComponent 的模块。
更新:
@Address 是一个 Qualifier 可以定义为:
@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Qualifier
public @interface Address {}
and 也可以替换为 @Named 限定符,例如@Named("地址").
有关限定符的详细信息,请参阅 Dagger Guide。
@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 ");
}
}
从2.31版本的Dagger 2开始,就有了辅助注入的机制。我们可以为此使用@AssistedInject。您可以在文档 here.
中查看更多信息示例:
我们有一个 class 需要辅助注射:
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;
}
}