10

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

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

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

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

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

  • Error: User Rate Limit Exceeded

    от
  • Error: User Rate Limit ExceededGuiceError: User Rate Limit ExceededProviderError: User Rate Limit ExceededSpringError: User Rate Limit Exceeded

    от
  • Error: User Rate Limit Exceeded

    от
  • 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.

  • 9

    В то время как &

    quot; вводить интерфейс & 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;), и, поскольку вы можете реализовать провайдеров самостоятельно, он все равно будет работать "вручную" вне пределов контекст (для тестирования, например).

  • 2

    Вот ответ @ jan-galinski, переделанный в 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!")
    }