15

I'm getting the following error in my code at launch:

Tried proxying com.bar.Foo to support a circular dependency, but it is not an interface.

How exactly does this proxying work? If I just throw enough classes behind interfaces, will everything be fine?

(I know that circular dependencies are usually a code smell, but I think in this case it's ok.)

Nick Heiner
  • 119,074
  • 188
  • 476
  • 699

3 Answers3

15

While the "inject an interface" approach is totally valid, and might even be the better solution in some occasions, in general, you can use a simpler solution: Providers.

For every class "A" guice can manage, guice also offers a "Provider<A>". This is an internal implementation of the javax.inject.Provider-interface, whose get() message will "return injector.getInstance(A.class)". You dont have to implement the Interface yourself, its part of the "guice magic".

Thus you can shorten the A->B, B-A example to:

public class CircularDepTest {

static class A {
    private final Provider<B> b;
    private String name = "A";

    @Inject
    public A(Provider<B> b) {
        this.b = b;
    }
}

static class B {

    private final Provider<A> a;
    private String name = "B";

    @Inject
    public B(Provider<A> a) {
        this.a = a;

    }
}

@Inject
A a;

@Inject
B b;

@Before
public void setUp() {
    Guice.createInjector().injectMembers(this);
}


@Test
public void testCircularInjection() throws Exception {
    assertEquals("A", a.name);
    assertEquals("B", a.b.get().name);
    assertEquals("B", b.name);
    assertEquals("A", b.a.get().name);
}}

I prefer this, because its more readable (you are not fooled to beleive that the constructor already holds an instance of "B") and since you could implement the Providers yourself, it would still work "by Hand", outside the guice context (for testing for example).

Jan Galinski
  • 11,768
  • 8
  • 54
  • 77
  • I also prefer this approach. It means that you don't have to create Interfaces when you otherwise wouldn't need them. It's also quicker/less potentially messy to change the injection to a Provider than create interfaces. – specialtrevor Nov 19 '13 at 16:30
  • How about having explicit dependency on `Guice` in you app code? I was always thinking it's nice to stay independent of DI framework. Before you introduced `Provider` to your code you could have switched to say `Spring` but this is not possible anymore. – Jan Zyka Jan 19 '15 at 09:30
  • 1
    I am talking about javax.inject.Provider, a JSR330 standard interface. No guice dependency required. – Jan Galinski Jan 19 '15 at 12:44
10

I'm new to this concept, but here's my understanding.

Let's say you have interfaces A and B, and implementations Ai and Bi.

If Ai has a dependency on B, and Bi has a dependency on A, then Guice can create a proxy implementation of A (call it Ap) that will at some point in the future be given an Ai to delegate to. Guice gives that Ap to Bi for its dependency on A, allowing Bi to finish instantiation. Then, since Bi has been instantiated, Guice can instantiate Ai with Bi. Then, since Ai is now good to do, Guice tells Ap to delegate to Ai.

If A and B were not interfaces (and you just had Ai and Bi) this just would not be possible, because creating Ap would require you to extend Ai, which already needs a Bi.

Here's what it might look like with code:

public interface A {
    void doA();
}

public interface B {
    void doB();
}

public class Ai implements A {

   private final B b;

   @Inject
   public Ai(B b) {
       this.b = b;
   }

   public void doA() {
       b.doB();
   }
}

public class Bi implements B {
   private final A a;

   @Inject
   public Bi(A a) {
       this.a = a;
   }

   public void doB() {
   }
}

The proxy class that Guice makes would look like this:

public class Ap implements A {
    private A delegate;
    void setDelegate(A a) {
        delegate = a;
    }

    public void doA() {
        delegate.doA();
    }
}

And it would all be wired using this basic idea:

Ap proxyA = new Ap();
B b = new B(proxyA);
A a = new A(b);
proxyA.setDelegate(a);

And here's what it would be like if you only had Ai and Bi, without interfaces A and B.

public class Ap extends Ai {
    private Ai delegate;

    public Ap() {
       super(_); //a B is required here, but we can't give one!
    }
}

If I just throw enough classes behind interfaces, will everything be fine?

I would guess that there are strict restrictions on how the proxy can be interacted with in the constructor. In other words, if B tries to call A before Guice has had a chance to populate A's proxy with the real A, then I would expect a RuntimeException.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
2

Here is @jan-galinski's answer, redone in Scala:

import javax.inject.Inject
import com.google.inject.{Guice, Injector, Provider}
import net.codingwell.scalaguice.InjectorExtensions._

/** Demonstrates the problem by failing with `Tried proxying CircularDep1$A to support a circular dependency, but it is not an interface.
  while locating CircularDep1$A for parameter 0 at CircularDep1$B.<init>(CircularDep.scala:10)
  while locating CircularDep1$B for parameter 0 at CircularDep1$A.<init>(CircularDep.scala:6)
  while locating CircularDep1$A` */
object CircularDep1 extends App {
  class A @Inject() (val b: B) {
    val name = "A"
  }

  class B @Inject() (val a: A) {
    val name = "B"
  }

  val injector: Injector = Guice.createInjector()
  val a: A = injector.instance[A]
  val b: B = injector.instance[B]

  assert("A" == a.name)
  assert("B" == a.b.name)
  assert("B" == b.name)
  assert("A" == b.a.name)
  println("This program won't run!")
}

/** This version solves the problem by using `Provider`s */
object CircularDep2 extends App {
  class A @Inject() (val b: Provider[B]) {
    val name = "A"
  }

  class B @Inject() (val a: Provider[A]) {
    val name = "B"
  }

  val injector: Injector = Guice.createInjector()
  val a: A = injector.instance[A]
  val b: B = injector.instance[B]

  assert("A" == a.name)
  assert("B" == a.b.get.name)
  assert("B" == b.name)
  assert("A" == b.a.get.name)
  println("Yes, this program works!")
}
Mike Slinn
  • 7,705
  • 5
  • 51
  • 85