简体   繁体   English

如何使用Dagger-2延迟注入接口?

[英]How to lazy-inject interfaces with Dagger-2?

I came across an instance where Dagger-2 wouldn't let me lazy inject. 我遇到了一个实例,其中Dagger-2不会让我偷懒注射。 It seems that it still requires me to supply the object at compile time. 似乎仍然需要我在编译时提供对象。 Why is that? 这是为什么?

The stacktrace: 堆栈跟踪:

[Dagger/MissingBinding] @javax.inject.Named("htfModel") de.wimj.core.Applications.IModel cannot be provided without an @Provides-annotated method.
[ERROR]       @javax.inject.Named("htfModel") de.wimj.core.Applications.IModel is injected at
[ERROR]           de.wimj.ui.Mt5Painter.<init>(…, htfTradeModel, …)
[ERROR]       dagger.Lazy<de.wimj.ui.Mt5Painter> is injected at
[ERROR]           de.wimj.core.Applications.ModelMqlBased.<init>(…, mt5Painter, …)
[ERROR]       dagger.Lazy<de.wimj.core.Applications.ModelMqlBased> is injected at
[ERROR]           de.wimj.di.components.trademodel.ModelModule.iModel(modelMqlBased, …)
[ERROR]       de.wimj.core.Applications.IModel is provided at
[ERROR]           de.wimj.di.components.trademodel.ModelComponent.createModel()

The code for the stacktrace: stacktrace的代码:

//Got it, Dagger-2 wants me to provide a IModel here
@ModelScope
@Component(modules = { ModelModule.class }, dependencies = { ClientComponent.class })
public interface ModelComponent {

    IModel createModel();

    @Component.Builder
    interface Builder {
        ModelComponent build();
        Builder clientComponent(ClientComponent clientComponent); //MT5Trader comes from this component
    }

}


//At this point I will provide the IModel. I do NOT get, why Dagger-2 forces
//me to provide a "ModelMqlBased" though. I obviously lazy-inject it. 
//I used this pattern in other cases as well (providing an interface and 
//lazy-injecting the possible instantiations as params)
@Module
public class ModelModule {

    @Provides
    @ModelScope
    IModel iModel(  Lazy<ModelMqlBased> modelMqlBased,  //lazy-injection here!
            ModelFileBased modelFileBased,
            @Named("configClientType")String clientType) {
        switch (clientType) {
        case "mqlBot": 
            return modelMqlBased.get();
        case "fileBot":
                return modelFileBased;
        default:
            throw new RuntimeException();
        }
    }
}

The following code should be irrelevant (the crux is the ModelModule), but for the sake of completion: 以下代码应该是无关的(关键是ModelModule),但是为了完整起见:

@ModelScope
public class ModelMqlBased implements IModel {

    @Inject
    public ModelMqlBased( Lazy<Mt5Painter> mt5Painter) {
        super();
        this.mt5Painter = mt5Painter.get();
    }

}

//this one sits in a "higher-scoped" component
@ClientScope
public class Mt5Painter {

    private IModel htfModel;
    private IModel ltfModel;

    @Inject
    public Mt5Painter(@Named("htfModel") Lazy<IModel> htfTradeModel, @Named("ltfModel") Lazy<IModel> ltfTradeModel) {
        super();
        this.htfModel = htfTradeModel.get();
        this.ltfModel = ltfTradeModel.get();
    }

Lazy<T> doesn't mean "figure out later whether T is bound", it means "ensure a binding exists for T at compile time, but only create an instance at runtime after I call get ". Lazy<T>意思不是“稍后确定T是否已绑定”,它的意思是“确保在编译时T已存在绑定,而仅在我调用get之后在运行时创建实例”。 You will still need to make the binding for T available in all cases, but Dagger won't try to create an instance of it until you explicitly ask for it. 在所有情况下,您仍然需要使T的绑定可用,但是Dagger在明确要求它之前不会尝试创建它的实例。

Dagger requires that for all uses of Provider<T> and Lazy<T> , the binding T needs to exist at compile time, even if at runtime you do not call it. Dagger要求对于Provider<T>Lazy<T>所有使用,绑定T必须在编译时存在,即使在运行时不调用它也是如此。 This ensures that if you do call get() on the Provider or Lazy instance, it does not fail at runtime for a binding it knew was missing at compile time. 这样可以确保,如果确实在Provider或Lazy实例上调用get() ,则它不会在运行时因其知道在编译时丢失的绑定而失败。 (Lazy behaves exactly as Provider does, except Lazy remembers the instance it returns regardless of whether the binding was scoped.) (Lazy的行为与Provider完全相同,但Lazy会记住它返回的实例,无论绑定是否作用域。)

This means that one of your options is to add a binding for ModelMqlBased that returns null or throws an Exception , which normally would be a terrible idea in Dagger, but it would be sufficient for a case where you know at runtime that the Provides method is never called. 这意味着您的选择之一是为ModelMqlBased添加一个绑定,该绑定返回null或引发Exception ,这在Dagger中通常是一个糟糕的主意,但是对于在运行时知道Provides方法是从来没有打电话。

A different way to achieve the flexibility you're looking for is with @BindsOptionalOf . @BindsOptionalOf是获得所需灵活性的另一种方法。 This allows you to inject an Optional<T> or Optional<Lazy<T>> , which resolves to a present value if the binding exists and an absent placeholder if the binding does not. 这允许您注入Optional<T>Optional<Lazy<T>> ,如果绑定存在,则解析为当前值;如果绑定不存在,则解析为当前值。

@Module
public abstract class ModelModule {
    // Note abstract class and static/abstract methods.

    @BindsOptionalOf
    abstract ModelMqlBased bindOptionalOfModelMqlBased();

    @Provides
    @ModelScope
    static IModel iModel(Optional<ModelMqlBased> modelMqlBased,
            ModelFileBased modelFileBased,
            @Named("configClientType")String clientType) {
        switch (clientType) {
        case "mqlBot": 
            return modelMqlBased.get();
        case "fileBot":
            return modelFileBased;
        default:
            throw new RuntimeException();
        }
    }
}

This may make it easier to reuse modules, especially because (like multibindings) you can supply as many @BindsOptionalOf abstract T bindOptionalOfT(); 这可能使重用模块更加容易,尤其是因为(如多重绑定)您可以提供尽可能多的@BindsOptionalOf abstract T bindOptionalOfT(); methods as you'd like and Dagger will not complain about the duplication. 您想要的方法和Dagger不会抱怨重复。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM