Инъекция зависимостей: определение области по регионам (Guice, Spring, что угодно)
Вот упрощенная версия моих потребностей.
У меня есть программа, где каждый объект B имеет свой собственный объект C и D, вводимый через Guice. Кроме того, объект A вводится в каждый объект C и D.
Что я хочу: для каждого объекта B его объекты C и D будут вставляться одним и тем же объектом A.
[Edit-Start]
(1) Guice поддерживает режимы "singleton" и "prototype". Тем не менее, мне нужно что-то среднее: мне нужно, чтобы A был одиночным WRT для данного объекта B (так что C и D, введенные в объект B, будут совместно использовать объект A). Для другого объекта B, я хочу другого A. Так что это singleton, но для ограниченного объема программы (на самом деле, ограниченная область структуры данных).
(2) Я не против решения, которое использует метод (setter) - или инъекцию поля.
(3) Я пытался, несколько раз, добиваться этого, и всегда считалось, что мне нужно реализовать какую-то обычную вещь из контейнера DI, чтобы сделать эту работу, но она никогда не срабатывала. Таким образом, я ищу подробное решение (а не только "ручное размахивание" )
[Edit-End]
В частности, я хочу, чтобы выход программы (ниже) был:
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);
}
}
Ответы
Ответ 1
Здесь одно решение на основе вашего исходного кода - есть три изменения:
- Переместите привязки для A, C и D в отдельный подмодуль
- Отметить A как одноэлемент в подмодуле
- Используйте метод @Provides в основном модуле для предоставления экземпляров BImpl с помощью
новый дочерний инжектор для каждого запроса - здесь находится подмодуль
Это работает, потому что привязка singleton для A теперь ограничена каждым дочерним инжектором.
[Примечание: вы всегда можете кэшировать экземпляр подмодуля в поле файла
main-module, если вы не хотите создавать его для каждого запроса 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);
}
}
Ответ 2
Теперь я не знаю, нужно ли вам иметь BImpl
, CImpl
и DImpl
, созданные Guice (например, для AOP), но если это не так:
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
, который вы используете с другой аннотацией привязки, вы можете использовать частный модуль, подобный этому, d добавить один раз для привязки аннотации при создании вашего инжектора:
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
PrivateModule и/или Scopes могут помочь, но я не уверен. Если нет, возможно, вам придется написать пользовательский поставщик для ваших объектов A.
Ответ 4
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, которые хорошо кэшируют результаты. И область очистки, которая очищает кеш, когда хочет новый объект. Не знаю, насколько это надежное решение, но я думаю, что это не так, когда дело доходит до Bs, которые хотят ввести Bs.
Я немного удивлен, что не мог получить что-то подобное, чтобы работать с детьми-инжекторами, но иногда я могу быть немного толстым.
Ответ 5
Не уверен в Guice, но Spring не будет иметь проблем с этим, поскольку beans может иметь разные области применения, например singleton (только один экземпляр, созданный по умолчанию), прототип (новый экземпляр 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>
Где, как эта конфигурация должна привести к 3 экземплярам 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.
Ответ 6
Я не знаком с guice, только spring. Я не думаю, что можно настроить механизм DI, чтобы сделать то, что вы пытаетесь достичь. Я вижу 2 решения:
- сделать объект B зависимым от (A, C, D) и ввести A в C и D во время выполнения.
- заставляют B полагаться только на A, а A зависит от C и D.
Ответ 7
Рассматривали ли вы изменение дизайна? Если C и D требуют одного и того же экземпляра A, то это предполагает, что между этими двумя классами существует некоторая общая ответственность.
Рассмотрите объединение их в один класс или извлеките части, которые манипулируют экземпляром A в новый класс.