依赖注入:按区域划分范围(Guice,Spring,Whatever)

时间:2010-06-10 12:06:29

标签: java spring dependency-injection guice

以下是我需要的简化版本。

我有一个程序,其中每个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);    
    }  
  }

7 个答案:

答案 0 :(得分:13)

以下是基于原始代码的一种解决方案 - 有三处变化:

  1. 将A,C和D的绑定移动到单独的子模块
  2. 将A标记为子模块中的单身人士
  3. 在主模块中使用@Provides方法提供BImpl的实例和
    每个请求的新子注入器 - 这是子模块进入的地方
  4. 这是有效的,因为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);    
        }  
      }
    

答案 1 :(得分:3)

现在,我不知道您是否绝对需要由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")));
}

我想也有其他可能性。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

  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,我的猜测并不是很好。 我有点惊讶我不能用这样的东西与儿童注射器一起工作,但有时我可能会有点厚。

答案 4 :(得分:0)

不确定Guice,但是Spring对beans can have different scopes没问题,例如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>

此配置应该导致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>

使用@Singleton注释看起来像Guice has the same concepts of scopes

答案 5 :(得分:0)

我不熟悉guice,只有春天。我不认为可以配置DI引擎来完成你想要实现的目标。我看到了两个解决方案:

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

答案 6 :(得分:0)

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

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