Внедрение зависимости: Определяя объем регионом (Guice, Spring, Безотносительно)

Вот упрощенная версия моих потребностей.

У меня есть программа, где каждый объект B имеет свой собственный объект C и D, введенный через Guice. Кроме того, объект введен в каждые объекты C и D.

Что я хочу: это для каждого объекта B, его объекты C и D будут введены с тем же объект.

[Запустить редактирование]

(1) Guice поддерживает "одноэлементные" и "опытные" режимы. Однако то, в чем я нуждаюсь, является чем-то промежуточным: Мне нужно, чтобы быть одиночным элементом WRT к данному объекту B (так, чтобы C и D, введенный в объект B, совместно использовали объект). Для другого объекта B я хочу другой A. Таким образом, это - одиночный элемент, но для ограниченного объема программы (на самом деле, ограниченного объема структуры данных).

(2) Я не возражаю против решения, которое использует метод (метод set) - или поле - инжекция.

(3) Я пытался, несколько раз, достигнуть этого, и это всегда чувствовало, что я только должен реализовать некоторую пользовательскую штуку контейнера DI для создания этой работы - но это никогда не работало. Таким образом я ищу подробное решение (не только "помахивание руки")

[Конец редактирования]

А именно, я хочу, чтобы вывод программы (ниже) был:

Created C0 with [A0]
Created D0 with [A0]
Created B0 with [C0, D0]
Created C1 with [A1]
Created D1 with [A1]
Created B1 with [C1, D1]

Где это в настоящее время производит следующий вывод:

Created C0 with [A0]
Created D0 with [A1]  <-- Should be A0
Created B0 with [C0, D0]
Created C1 with [A2]  <-- Should be A1
Created D1 with [A3]  <-- Should be A1
Created B1 with [C1, D1]

Я ожидаю, что контейнеры DI позволят этот вид настройки, но до сих пор у меня не было удачи в нахождении решения. Ниже мой находящийся в Guice код, но основанное на Spring (или другой основанный на контейнерах DI) решение приветствуется.

  import java.util.Arrays;
  import com.google.inject.*;

  public class Main {

    public static class Super {
      private static Map<Class<?>,Integer> map = new HashMap<Class<?>,Integer>();

      private Integer value;

      public Super(Object... args) {
        value = map.get(getClass());
        value = value == null ? 0 : ++value;
        map.put(getClass(), value);

        if(args.length > 0)
          System.out.println("Created " + this + " with " + Arrays.toString(args));
      }

      @Override
      public final String toString() {
        return "" + getClass().getSimpleName().charAt(0) + value;
      }
    }

    public interface A { }  
    public static class AImpl extends Super implements A  { } 

    public interface B { }  
    public static class BImpl extends Super implements B {
      @Inject public BImpl(C c, D d) { super(c,d); }
    }

    public interface C { }  
    public static class CImpl extends Super implements C  {
      @Inject public CImpl(A a) { super(a); }
    }

    public interface D { }  
    public static class DImpl extends Super implements D {
      @Inject public DImpl(A a) { super(a); }
    }


    public static class MyModule extends AbstractModule {
      @Override
      protected void configure() {
        bind(A.class).to(AImpl.class);
        bind(B.class).to(BImpl.class);      
        bind(C.class).to(CImpl.class);      
        bind(D.class).to(DImpl.class);      
      }    
    }

    public static void main(String[] args) {
      Injector inj = Guice.createInjector(new MyModule());
      inj.getInstance(B.class);    
      inj.getInstance(B.class);    
    }  
  }
13
задан Itay Maman 10 June 2010 в 13:47
поделиться

7 ответов

Вот одно решение, основанное на вашем оригинальном коде - есть три изменения:

  1. Переместите привязки для A, C и D в отдельный подмодуль
  2. Пометьте A как singleton в подмодуле
  3. Используйте метод @Provides в основном модуле, чтобы предоставить экземплярам BImpl
    новым дочерним инжектором для каждого запроса - вот где вступает в дело подмодуль

Это работает, потому что привязка singleton для A теперь ограничена каждым дочерним инжектором.

[ Примечание: вы всегда можете кэшировать экземпляр подмодуля в поле
основного модуля, если вы не хотите продолжать создавать его для каждого запроса B ]

  import java.util.*;
  import com.google.inject.*;

  public class Main {

    public static class Super {
      private static Map<Class<?>,Integer> map = new HashMap<Class<?>,Integer>();

      private Integer value;

      public Super(Object... args) {
        value = map.get(getClass());
        value = value == null ? 0 : ++value;
        map.put(getClass(), value);

        if(args.length > 0)
          System.out.println("Created " + this + " with " + Arrays.toString(args));
      }

      @Override
      public final String toString() {
        return "" + getClass().getSimpleName().charAt(0) + value;
      }
    }

    public interface A { }  
    public static class AImpl extends Super implements A  { } 

    public interface B { }  
    public static class BImpl extends Super implements B {
      @Inject public BImpl(C c, D d) { super(c,d); }
    }

    public interface C { }  
    public static class CImpl extends Super implements C  {
      @Inject public CImpl(A a) { super(a); }
    }

    public interface D { }  
    public static class DImpl extends Super implements D {
      @Inject public DImpl(A a) { super(a); }
    }

    public static class MyModule extends AbstractModule {
      @Override
      protected void configure() {} 

  // >>>>>>>>
      @Provides
      B builder( Injector injector ) {
        return injector.createChildInjector( new SubModule() ).getInstance( BImpl.class );
      }
  // <<<<<<<<
    }

  // >>>>>>>>
    public static class SubModule extends AbstractModule {
      @Override
      protected void configure() {
        bind(A.class).to(AImpl.class).in( Scopes.SINGLETON );
        bind(C.class).to(CImpl.class);      
        bind(D.class).to(DImpl.class);      
      }    
    }
  // <<<<<<<<

    public static void main(String[] args) {
      Injector inj = Guice.createInjector(new MyModule());
      inj.getInstance(B.class);    
      inj.getInstance(B.class);    
    }  
  }
13
ответ дан 1 December 2019 в 23:30
поделиться

Я не знаю, действительно ли вам нужно иметь BImpl , CImpl и DImpl , созданные Guice ( чтобы разрешить АОП, например), но если нет, то это просто:

public static class MyModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(A.class).to(AImpl.class);
  }

  @Provides
  protected B provideB(A a) {
    C c = new CImpl(a);
    D d = new DImpl(a);
    return new BImpl(c, d);
  }
}

В качестве альтернативы (и я знаю, что вы не указали это в своем вопросе), если вы можете связать каждый экземпляр B , который если вы используете другую аннотацию привязки, вы можете использовать такой частный модуль, который вы добавляете один раз для каждой аннотации привязки при создании инжектора:

public static class MyOtherModule extends PrivateModule {
  private final Annotation annotation;

  public MyOtherModule(Annotation annotation) {
    this.annotation = annotation;
  }

  @Override
  protected void configure() {
    bind(A.class).to(AImpl.class).in(Scopes.SINGLETON);
    bind(C.class).to(CImpl.class);
    bind(D.class).to(DImpl.class);
    bind(B.class).annotatedWith(annotation).to(BImpl.class);
    expose(B.class).annotatedWith(annotation);
  }
}

main для этого выглядит так:

public static void main(String[] args) {
  Injector inj = Guice.createInjector(new MyOtherModule(Names.named("first")),
      new MyOtherModule(Names.named("second")));
  inj.getInstance(Key.get(B.class, Names.named("first")));
  inj.getInstance(Key.get(B.class, Names.named("second")));
}

Я полагаю, что есть и другие возможности.

3
ответ дан 1 December 2019 в 23:30
поделиться

Не уверен насчет Guice, но у Spring не будет проблем с этим, поскольку bean-компоненты могут иметь разные области действия , такие как singleton (создан только один экземпляр, по умолчанию), prototype (создается новый экземпляр bean-компонента каждый раз, когда на него ссылаются и т. д.

Например, следующая конфигурация XML приведет к одному экземпляру Foo и трем экземплярам Bar .

<bean id="Foo" class="com.name.Foo"/>

<bean id="Bar1" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

<bean id="Bar2" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

<bean id="Bar3" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

Где как эта конфигурация должна приведет к появлению трех экземпляров Bar , каждый из которых имеет свой экземпляр Foo .

<bean id="Foo" class="com.name.Foo" scope="prototype" />

<bean id="Bar1" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

<bean id="Bar2" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

<bean id="Bar3" class="com.name.Bar">
    <property name="foo" ref="Foo"/>
</bean>

Похоже, Guice использует те же концепции областей видимости , что и @Singleton аннотация.

0
ответ дан 1 December 2019 в 23:30
поделиться
  import java.util.*;
  import com.google.inject.*;
  import com.google.inject.name.*;

  public class Main {

    public static class Super {
      private static Map<Class<?>,Integer> map = new HashMap<Class<?>,Integer>();

      private Integer value;

      public Super(Object... args) {
        value = map.get(getClass());
        value = value == null ? 0 : ++value;
        map.put(getClass(), value);

        if(args.length > 0)
          System.out.println("Created " + this + " with " + Arrays.toString(args));
      }

      @Override
      public final String toString() {
        return "" + getClass().getSimpleName().charAt(0) + value;
      }
    }

    public interface A { }  
    public static class AImpl extends Super implements A  { } 

    public interface B { }  
    public static class BImpl extends Super implements B {
      @Inject public BImpl(C c, D d) { super(c,d); }
    }

    public interface C { }  
    public static class CImpl extends Super implements C  {
      @Inject public CImpl( A a) { super(a); }
    }

    public interface D { }  
    public static class DImpl extends Super implements D {
      @Inject public DImpl(A a) { super(a); }
    } 


    public static class MyModule extends AbstractModule {
      @Override
      protected void configure() {
        CachingScope cachedScope = new CachingScope();
        bind(C.class).to(CImpl.class);      
        bind(D.class).to(DImpl.class);  
        bind(B.class).to(BImpl.class).in(new ClearingScope(cachedScope));
        bind(A.class).to(AImpl.class).in(cachedScope);
      }
    }

    public static class CachingScope implements Scope {
        List<CacheProvider<?>> providers = new LinkedList<CacheProvider<?>>();
        public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
            CacheProvider<T> t = new CacheProvider<T>(unscoped);
            providers.add(t);
            return t;
        }

        public void clear() {
            for(CacheProvider c : providers) {
                c.clear();
            }
        }
    }

    public static class ClearingScope implements Scope {
        CachingScope scopeToClear;
        ClearingScope(CachingScope scopeToClear) {
            this.scopeToClear = scopeToClear;
        }
        public <T> Provider<T>  scope(Key<T> key, Provider<T> unscoped) {
            return new ClearingProvider<T>(unscoped, scopeToClear);
        }
    }

    public static class CacheProvider<T> implements Provider<T> {
        T t;
        Provider<T> unscoped;
        CacheProvider(Provider<T> unscoped) {
            this.unscoped = unscoped;
        }
        public T get() {
            if(t == null) {
                t = unscoped.get();
            }
            return t;
        }

        public void clear() {
            t = null;
        }
    }
    public static class ClearingProvider<T> implements Provider<T> {
        Provider<T> unscoped;
        CachingScope scopeToClear;
        ClearingProvider(Provider<T> unscoped, CachingScope scopeToClear) {
            this.unscoped = unscoped;
            this.scopeToClear = scopeToClear;
        }
        public T get() {
            scopeToClear.clear();
            return unscoped.get();
        }
    }


    public static void main(String[] args) {
      Injector inj = Guice.createInjector(new MyModule());
      inj.getInstance(B.class);    
      System.out.println("--");
      inj.getInstance(B.class);    
    }  
  }

Что ж, это была забавная игра в API. Мне не очень нравится это решение, но я думаю, что оно работает. Теперь у вас есть две новые области: CachingScope, которая хорошо кэширует результаты. И область очистки, которая очищает кеш, когда ему нужен новый объект. Не представляя, насколько надежным является это решение, я предполагаю, что будет не очень, когда дело доходит до B, которые хотят, чтобы B были введены. Я немного удивлен, что не смог заставить что-то подобное работать с детскими инжекторами, но иногда я могу быть немного толстым.

1
ответ дан 1 December 2019 в 23:30
поделиться

Я не знаком с guice, только с spring. Я не думаю, что можно настроить движок DI на то, чего вы пытаетесь достичь. Я вижу 2 решения:

  1. сделать объект B зависимым от (A, C, D) и инжектировать A в C и D во время выполнения.
  2. сделать B зависимым только от A, а A зависимым от C и D.
0
ответ дан 1 December 2019 в 23:30
поделиться

Думали ли вы об изменении дизайна? Если C и D требуют одного и того же экземпляра A, это означает, что между этими двумя классами существует некоторая общая ответственность.

Рассмотрите возможность объединения их в один класс или извлеките части, которые управляют экземпляром A, в новый класс.

0
ответ дан 1 December 2019 в 23:30
поделиться

PrivateModule и / или Scopes могут помочь, но я не уверен. В противном случае вам, возможно, придется написать собственный поставщик для ваших объектов A.

1
ответ дан 1 December 2019 в 23:30
поделиться
Другие вопросы по тегам:

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