dependency injection:按区域划分范围(Guice,Spring,Whatever)

这是我需求的简化版本。

我有一个程序,其中每个B对象都有自己的C和D对象,通过Guice注入。 此外,A对象被注入每个C和D对象。

我想要的是 :对于每个B对象,它的C和D对象将被注入相同的A对象。

[编辑-开始]

(1)Guice支持“单例”和“原型”模式。 但是,我需要的是介于两者之间:我需要A对给定的B对象进行单例WRT(这样注入B对象的C和D将共享一个A对象)。 对于另一个B对象,我想要另一个A.所以它是一个单例,但对于程序的有限范围(实际上,数据结构的范围有限)。

(2)我不介意使用方法(setter)或现场注入的解决方案。

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

这是基于原始代码的一种解决方案 – 有三种变化:

  1. 将A,C和D的绑定移动到单独的子模块中
  2. 将A标记为子模块中的单例
  3. 在主模块中使用@Provides方法来提供BImpl的实例
    每个请求的新子注入器 – 这是子模块的用武之地

这是有效的,因为A的单例绑定现在仅限于每个子注入器。

[注意:你总是可以将子模块实例缓存在一个字段中
主模块,如果你不想继续为B的每个请求创建它]

  import java.util.*; import com.google.inject.*; public class Main { public static class Super { private static Map,Integer> map = new HashMap,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); } } 

现在,我不知道您是否绝对需要由Guice创建BImplCImplDImpl (例如,允许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每个实例,你可以使用这样的私有模块,你可以为每个绑定添加一次创建进样器时的注释:

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

我想也有其他可能性。

PrivateModule和/或Scopes可能会有所帮助,但我不确定。 如果没有,您可能必须为A对象编写自定义提供程序。

  import java.util.*; import com.google.inject.*; import com.google.inject.name.*; public class Main { public static class Super { private static Map,Integer> map = new HashMap,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> providers = new LinkedList>(); public  Provider scope(Key key, Provider unscoped) { CacheProvider t = new CacheProvider(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  Provider scope(Key key, Provider unscoped) { return new ClearingProvider(unscoped, scopeToClear); } } public static class CacheProvider implements Provider { T t; Provider unscoped; CacheProvider(Provider unscoped) { this.unscoped = unscoped; } public T get() { if(t == null) { t = unscoped.get(); } return t; } public void clear() { t = null; } } public static class ClearingProvider implements Provider { Provider unscoped; CachingScope scopeToClear; ClearingProvider(Provider 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,我的猜测并不是很好。 我有点惊讶我不能得到这样的东西与儿童注射器一起工作,但随后我可能会有点厚。

不确定Guice,但Spring对此没有任何问题,因为bean可以有不同的范围 ,例如singleton(只创建一个实例,默认),prototype(每次引用时都会创建一个新的bean实例,等等)。

例如,以下XML配置将导致一个Foo实例和三个Bar实例。

           

此配置应该导致3个Bar实例,每个实例都有不同的Foo实例。

           

看起来Guice与@Singleton注释具有相同的范围概念 。

我不熟悉guice,只有spring。 我认为不可能配置DI引擎来做你想要实现的目标。 我看到2个解决方案:

  1. 使B对象依赖于(A,C,D)并在运行时将A注入C和D.
  2. 使B仅依赖于A,A依赖于C和D.

你考虑过改变你的设计吗? 如果C和D需要相同的A实例,则表明这两个类之间存在一些共同责任。

考虑将它们组合成一个类,或者将操作A实例的部分提取到一个新类中。