views:

330

answers:

7

Here's a simplified version of my needs.

I have a program where every B object has its own C and D object, injected through Guice. In addition an A object is injected into every C and D objects.

What I want: that for each B object, its C and D objects will be injected with the same A object.

[Edit-Start]

(1) Guice supports "singleton" and "prototype" modes. However, what I need is something in between: I need A to be singleton WRT to a given B object (so that the C and D injected into a B object will share an A object). For another B object, I want another A. So it's a singleton but for a limited scope of the program (actually, a limited scope of a data structure).

(2) I don't mind a solution that uses method (setter)- or field- injection.

(3) I tried, several times, to achieve this and it always felt that I only need to implement some custom thingy of the DI container to make this work - but it never worked. Thus, I am looking for a detailed solution (not just "hand waving")

[Edit-End]

Specifically, I want the output of the program (below) to be:

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]

Where it currently produces the following output:

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]

I am expecting DI containers to allow this kind of customization but so far I had no luck in finding a solution. Below is my Guice-based code, but a Spring-based (or other DI containers-based) solution is welcome.

  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: 

PrivateModule and/or Scopes might help, but I'm not sure. If not, you may have to write a custom provider for your A objects.

dty
I am looking for a precise answer. I know that in general it should be possible to achieve that by implementing some custom provider/generator/injector. I am asking about the details....
Itay
A: 

Not sure about Guice, but Spring would have no problem with this as beans can have different scopes, such as singleton (only one instance created, the default), prototype (a new bean instance is created every time it is referenced, etc.

For example, the following XML configuration would result in one instance of Foo and three Bar instances.

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

Where as this configuration should result in 3 instances of Bar, each of which has a different Foo instance.

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

Looks like Guice has the same concepts of scopes with the @Singleton annotation.

matt b
Of course Guice supports "singleton" and "prototype" modes. However, what I need is something in between: I need A to be singleton WRT to a given B object (so that the C and D injected into a B object will share an A object). For another B object, I want another A. So it's a singleton but for a limited scope of the program (actually, a limited scope of a data structure).
Itay
I think Spring allows you to create new types of scope. Never tried myself though.
Donal Fellows
Does Guice allow you to control how instances are wired up, like Spring's explicit XML configuration allows? For example, in the above snippets I could simply control how many A's I created, which I shared them with, etc. Or is Guice fully automatic?
matt b
@Matt - Guice does not support anything like the explicit XML config. For most cases you would use a annotations to specify which implementation gets inserted where but for this kind of Robot Legs problem you should be able to use Private Modules, but I could not get them to work.http://code.google.com/p/google-guice/wiki/FrequentlyAskedQuestions#How_do_I_build_two_similar_but_slightly_different_trees_of_objec
mlk
+1  A: 
  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);    
    }  
  }

Well, that was a fun play in the APIs. I don't overly like this solution, but I think it work. You now have two new scopes, a CachingScope that, well caches results. And a clearing scope that clears a Cache when it wants a new object. No idea how robust this solution is, my guess would be not very when it comes to Bs that want Bs injected. I'm a little surprised I could not get something like this to work with child injectors, but then I can be a bit thick at times.

mlk
I assume you already got it, but just to make sure: I don't want a singleton (see my Edit at the question)
Itay
edited the answer.
mlk
"I'm a little surprised I could get something like this to work with child injectors" did you want to say "Couldn't?"
Itay
Anyway, thanks. This is the most serious answer so far.
Itay
Could/could not - I was aiming for could not.The more I think about it, the more I think clever scopes or a smart module is the way to go.
mlk
A: 

I'm not familiar with guice, only spring. I do not think it is possible to configure DI engine to do what you're trying to achieve. I see 2 solutions:

  1. make B object to depended on (A, C , D) and inject A into C and D at runtime.
  2. make B to be depended on A only , and A depend on C and D.
mtim
Let's assume we're using method injection (insrtead of the constructor injection in the code I posted). Then, theoretically, if the DI container will allow B instances to get be "aware" of the injector then they will be able to configure it further thus affecting how C,D (and their A's) are created.
Itay
A: 

Have you considered changing your design? If C and D require the same instance of A then that suggests there is some shared responsibility between those two classes.

Consider combining them into one class, or extract the parts that manipulate the A instance into a new class.

Jonathan
Itay
+3  A: 

Now, I don't know if you absolutely need to have BImpl, CImpl and DImpl created by Guice (to allow for AOP, for example), but if not this is simple:

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

Alternatively (and I know you didn't specify this in your question), if you can bind each instance of B that you use with a different binding annotation, you can use a private module like this, which you'd add once per binding annotation when creating your injector:

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 for this looks like:

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

I imagine there are other possibilities as well.

ColinD
Good answer, regarding the custom provider - if you do need C and D to be injected by Guice then you could remove A from the dependencies C and D require then via a manual setter inject just that.
mlk
@mlk Yes, that would work well. Also, if he was willing to have the `C` and `D` interfaces expose a `setA(A)` method (which I think I'd be reluctant to do), `BImpl` could just take an `A`, `C` and `D` in its constructor and set the `A` itself.
ColinD
+4  A: 

Here's one solution based on your original code - there are three changes:

  1. Move the bindings for A, C, and D into a separate sub-module
  2. Mark A as a singleton in the sub-module
  3. Use a @Provides method in the main-module to provide instances of BImpl with
    a new child injector for each request - this is where the sub-module comes in

This works because the singleton binding for A is now limited to each child injector.

[ Note: you could always cache the sub-module instance in a field of the
main-module if you don't want to keep creating it for each request of 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);    
    }  
  }
mcculls
BTW, you could also create a new top-level injector instead of creating a child injector... `return Guice.createInjector( new SubModule() ).getInstance( BImpl.class );`... as long as the sub-module doesn't need any bindings from the calling injector. It's the new injector that's important, because singletons are always scoped per-injector (per-key).
mcculls
Great answer! Just what I was looking for
Itay