Вопрос по java, dependency-injection, guice – Guice прокси для поддержки циклической зависимости

10

Я получаю следующую ошибку в своем коде при запуске:

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

Как именно работает этот прокси? Если я просто выброшу достаточно классов за интерфейсами, все будет хорошо?

(Я знаю, что циклические зависимости обычно являются запахом кода, но я думаю, что в этом случае все в порядке.)

Ваш Ответ

3   ответа
9

вводить интерфейс & quot; Подход полностью действителен, и в некоторых случаях может даже оказаться лучшим решением, в общем, вы можете использовать более простое решение: провайдеры.

Для каждого класса "A" guice может управлять, guice также предлагает & quot;Provider<A>& Quot ;. Это внутренняя реализация интерфейса javax.inject.Provider, чьяget() сообщение будет & quot;return injector.getInstance(A.class)& Quot ;. Вам не нужно реализовывать Интерфейс самостоятельно, его часть «волшебной маскировки».

Таким образом, вы можете сократить пример A-> B, B-A до:

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);
}}

Я предпочитаю это, потому что это более читабельно (вы не обманываетесь, полагая, что конструктор уже содержит экземпляр & quot; B & quot;), и, поскольку вы можете реализовать провайдеров самостоятельно, он все равно будет работать "вручную" вне пределов контекст (для тестирования, например).

Error: User Rate Limit ExceededGuiceError: User Rate Limit ExceededProviderError: User Rate Limit ExceededSpringError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
2

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!")
}
8

Допустим, у вас есть интерфейсыA а такжеBи реализацииAi а такжеBi.

ЕслиAi имеет зависимость отB, а такжеBi имеет зависимость отAтогда Guice может создать прокси реализациюA (назови этоAp) что в какой-то момент в будущем будетAi делегировать. Guice дает этоAp вBi за его зависимость отA, позволяяBi закончить создание экземпляра Тогда, так какBi был создан экземпляр, Guice может создать экземплярAi сBi, Тогда, так какAi теперь хорошо, Guice говоритAp делегироватьAi.

ЕслиA а такжеB не были интерфейсы (а у вас просто былоAi а такжеBi) это просто не было бы возможно, потому что созданиеAp потребует от вас продлитьAiкоторый уже нуженBi.

Вот как это может выглядеть с кодом:

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() {
   }
}

Прокси-класс, который создает Guice, будет выглядеть так:

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

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

И все это будет связано с использованием этой основной идеи:

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

И вот как это было бы, если бы вы имелиAi а такжеBiбез интерфейсовA а также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?

Я полагаю, что существуют строгие ограничения на взаимодействие прокси в конструкторе. Другими словами, если B попытается вызвать A до того, как у Guice будет возможность заполнить прокси A действительным A, то я ожидаю исключение RuntimeException.

Похожие вопросы