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

2022-09-04 20:15:30

这是我需求的简化版本。

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

下面是一个基于原始代码的解决方案 - 有三个更改:

  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<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

现在,我不知道你是否绝对需要拥有,并由Guice创建(例如,允许AOP),但如果不是,这很简单:BImplCImplDImpl

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

我想还有其他可能性。


推荐