Вот упрощенная версия моих потребностей.
У меня есть программа, где каждый объект 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);
}
}
Вот одно решение, основанное на вашем оригинальном коде - есть три изменения:
Это работает, потому что привязка 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);
}
}
Я не знаю, действительно ли вам нужно иметь 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")));
}
Я полагаю, что есть и другие возможности.
Не уверен насчет 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 аннотация.
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 были введены. Я немного удивлен, что не смог заставить что-то подобное работать с детскими инжекторами, но иногда я могу быть немного толстым.
Я не знаком с guice, только с spring. Я не думаю, что можно настроить движок DI на то, чего вы пытаетесь достичь. Я вижу 2 решения:
Думали ли вы об изменении дизайна? Если C и D требуют одного и того же экземпляра A, это означает, что между этими двумя классами существует некоторая общая ответственность.
Рассмотрите возможность объединения их в один класс или извлеките части, которые управляют экземпляром A, в новый класс.
PrivateModule и / или Scopes могут помочь, но я не уверен. В противном случае вам, возможно, придется написать собственный поставщик для ваших объектов A.