Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dagger 2 circular dependency

Tags:

java

dagger-2

In a project I am working on I have 2 classes that are highly dependent on one another:

@Singleton
class WorkExecutor {
    @Inject Provider<ExecutionServices> services;
    ...
    public void execute(Work w){
        w.execute(services.get());
        ...
    }
    ...
}

class ExecutionServicesImpl implements ExecutionServices {
    @Inject WorkExecutor executor;
    ...
}

The idea is that when executing a work, the work has access to several services - one of them is the executor itself so that a work will be able to execute sub works.

As one can see, there is a circular dependency here, but one that I found very hard to break.

The main problem is that the WorkExecutor does not actually need an instance of ExecutionServices object at graph-constructing time but only a provider to be used later. sadly, Dagger does not know that the WorkExecutor will not call the ExecutionServices provider from the constructor of the class so it guesses that ExecutionServices depends on WorkExecutor and vice versa.

One possible solution that I found is to define a module and component in the following way:

interface DelayedProvider<T> extends Provider<T>{}

@Module
class AppModule {
    Provider<ExecutionServices> delayedProvider = null;

    @Provides DelayedProvider<ExecutionServices> provideDelayed() {
        return () -> delayedProvider.get();
    }

    @Provides @Named("late-binding-conf") Void latebindingConf(Provider<ExecutionServices> eager){
        this.delayedProvider = eager;
        return null; //notice we returning Void and not void
    }
}

@Component(modules=AppModule.class)
interface AppComponent {
    App app();
    @Named("late-binding-conf") Void configureLateBinding();
}

and then I modifies the original classes to be:

@Singleton
class WorkExecutor {
    @Inject DelayedProvider<ExecutionServices> services;
    ...
    public void execute(Work w){
        w.execute(services.get());
        ...
    }
    ...
}

class ExecutionServicesImpl implements ExecutionServices {
    @Inject WorkExecutor executor;
    ...
}

And then in order to create my app I have to do:

AppComponent acomp = DaggerAppComponent.create();
App = acomp.app();
acomp.configureLateBinding();

But I am not sure this is the proper course of action - Is there a better way?

like image 969
bennyl Avatar asked May 08 '15 10:05

bennyl


People also ask

How do you resolve a circular dependency dagger?

We can break the circular dependency in two ways: either remove one of the @Inject constructors or inject a Lazy (or Provider ) into one of the classes. One of the limitations of this approach is the inability to use lazily injected class inside the constructor (or init block).

Are circular dependencies OK?

and, yes, cyclic dependencies are bad: They cause programs to include unnecessary functionality because things are dragged in which aren't needed. They make it a lot harder to test software. They make it a lot harder to reason about software.

What is dependency injection dagger?

The term dependency injection context is typically used to describe the set of objects which can be injected. In Dagger 2, classes annotated with @Module are responsible for providing objects which can be injected. Such classes can define methods annotated with @Provides .


1 Answers

I don't suspect OP will like this, as you want a way to make something 'wrong', work 'right'. That can't really be. Whenever you encounter a circular dependency, the "right" solution is to refactor to remove that dependency.

In your case, WorkExecutor is a singleton so it probably needs to remain as is. ExecutionServicesImpl should then be modified to remove the dependency on WorkExecutor. Without knowing the specifics of the code, one can't say too much more. However, having an ExecutionService be independent from it's "worker" is reduced coupling and likely a very good thing in the long run.

like image 78
JoeG Avatar answered Oct 08 '22 13:10

JoeG