Using Guice with circular dependencies
-
18-09-2019 - |
Question
Consider this simple example.
Class A {
B b;
A() {
this.b = new B(this);
}
}
In this example instance A knows about instance B, and instance B knows about instance A.
My question is: how to instantiate instance A with Guice, i.e. how to make Guice take care of this complex circle dependencies?
Solution
To answer your first question "how to instantiate instance A with Guice": you can simply add @Inject
to the constructor:
class A {
private final B b;
@Inject
A() {
this.b = new B(this);
}
}
This works because the API for creating A
doesn't have a circular dependency. Guice will just use the A
constructor any time it needs to create or inject an A
object.
If your question is how to use Guice to create an object where the API for creating the object has a circular dependency, see this blog post by Misko Hevery (as mentioned in Yury's answer).
OTHER TIPS
Your example is not an issue at all, since you're constructing B directly. But if you want to both A and B to be created by Guice, one or both should be an interface. You can do:
public interface A { /* skipping methods */ }
public interface B { /* skipping methods */ }
public class AImpl implements A {
private final B b;
@Inject
public AImpl(B b) {
this.b = b;
}
// ...
}
public class BImpl implements B {
private final A a;
@Inject
public BImpl(A a) {
this.a = a;
}
// ...
}
Even if AImpl
and BImpl
are scoped as singletons, Guice can handle this injection (by way of a proxy). This works in a simple case like this at any rate... I imagine there could be more complex circular dependencies that it couldn't handle. Anyway, eliminating circular dependencies would be preferable, of course.
The answer is that you should not use a dependency injection framework while you have circular dependences in your code.
So, you have to refactor you code beforehand. As far as I know, there are two solutions for tightly coupled classes: either merge two classes into one or introduce new class and move common logic into it (for detail look here)
I think that NamshubWriter's proposal isn't very guicy. I think that in Guice, a constructor should do exactly one thing: assign parameters into fields. If there's anything else you need to do, put it into a factory or a provider.
In this case, we'll want a provider for A. The provider could directly call new B(), but then we'd directly couple A to B, which is what we tried to avoid in the first place. So we indirect the creation of B over a factory, which guice can provide for us via assistedInject. This code runs and compiles fine, and completely decouples A and B.
In a realistic scenario, you'd need to hide A and B behind interfaces to take advantage of the separation.
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.FactoryProvider;
public class Try {
public static void main(String[] args) {
System.out.println(
Guice.createInjector(new MyModule()).getInstance(A.class)
);
}
}
class MyModule extends AbstractModule {
public void configure() {
bind(A.class).toProvider(AProvider.class);
bind(IBFactory.class).toProvider(
FactoryProvider.newFactory(IBFactory.class, B.class));
}
}
class A {
B b;
public void setB(B b) {
this.b = b;
}
}
class B {
A a;
@Inject
B(@Assisted A a) {
this.a = a;
}
}
class AProvider implements Provider<A> {
private final IBFactory bFactory;
@Inject
AProvider(IBFactory bFactory) {
this.bFactory = bFactory;
}
public A get() {
A a = new A();
a.setB(bFactory.create(a));
return a;
}
}
interface IBFactory {
public B create(A a);
}