Injecting a Bar with an already existing Foo

Viewed 79

Consider the existence of a Foo some time in the app. Now Bar objects depend on a Foo. I want to create this bar based on this foo I already have, but Guice creates a new instance of Foo every time. I can have a lot of Foos, so I cannot @Singleton Foo.

I tried to use scopes for this purpose, but I don't know what I am doing wrong.

I know this can be solved with @AssistedInject but I do not want to take this solution because these associations are common in my context and the dependency is only one. Also, since I am working on creating a tool based on Guice, all I got is a Foo, and Bar.class and I want to create instances of Bar with the given Foo. I am not aware if there are any factories or so.

public class InjectionTest2 {

    public static void main(String[] args) {
        Module module = new AbstractModule() {
            ReceiverScope receiverScope = new ReceiverScope();

            @Override
            protected void configure() {
                bindScope(ReceiverScoped.class, receiverScope);
                bind(Foo.class);
            }

            @Provides
            @Singleton
            public ReceiverScope scope() {
                return receiverScope;
            }
        };

        Injector injector = Guice.createInjector(module);
        Foo foo = injector.getInstance(Foo.class); //assume a given foo

        ReceiverScope scope = injector.getInstance(ReceiverScope.class);
        scope.enter();
        scope.seed(Key.get(Foo.class, Existing.class), foo);
        Bar bar = injector.getInstance(Bar.class);//Create a Bar with this foo
        scope.exit();

    }

    private static class Foo {
        @Inject
        public Foo() {
            System.out.println("foo created" + toString());
        }
    }

    @ReceiverScoped
    private static class Bar {
        @Inject
        public Bar(@Existing Foo foo) {
            System.out.println("bar created with foo" + foo.toString());
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @BindingAnnotation
    private static @interface Existing {
    }
}

ReceiverScope is a class identical to Simple Scope given here.

Another way I can think of is if I manually iterate Bar's contructor parameters and for each parameter, I ask guice to give me an instance, but if it is a Foo, I give my value. But won't this give me a very hard time? I mean I will have to consider a lot of things, because in the constructor might be extra Qualifiers on parameters and stuff...

EDIT (after @kendavidson's answer):

This is true. I thought it makes my question more complicated, that's why I did not include it. Bar is an interface. Fields in Foo contain an annotation which has a Class<? extends Bar> barClass() method. For example:

@ContainsBars
private static class Foo {
    @MakeBar(BlueBar.class)
    private Something aSomething;
    
    @MakeBar(BlackBar.class)
    private SomethingElse aSomethingElse;
    
    
    @Inject
    public Foo() {
        //create aSomething
        //create aSomethingElse
    }
}

Now, whenever Guice injects a @ContainsBars, I listen it via a TypeListener and I go to create the bars. I scan Foo.class and I get BlueBar.class & BlackBar.class.

The problem I am trying to solve is, how to create BlueBar when BlueBar has a dependency to Foo.class? Whenever this happens, I need to create the BlueBar with the Foo I was taken from the listener.

Reflecting constructor was one of my thoughts however it sounds a pain in the ... I guess, it will need to iterate each parameter of the @Inject constructor of BlueBar.class. If the parameter is of type Foo, I give my Foo. If it is a different type, ask the injector to give me an instance of that type. The thing is, how do I do this. When Guice injects, it takes in consideration possible binding annotations (Qualifiers) and generics. Doing that again, sounds pain.

In the other hand, these @ContainsBars annotated classes by 99% chance have no relation to each other.

Adding a method to Bar interface like setFoo(Foo f) will not work. It will have to be a generic. Therefore, method injection with a setter will be cast unsafe. As far as I am aware of, and assuming BlackBar implements Bar<Foo>, when I will Bar b = injector.getInstance(BlackBar.class), It will give me a Bar<?>. That means, I can't call the setter safely.

Field injection is interesting. Having something like this:

public class BlackBar implements Bar {
        private MyDepedency dep;
        
        @Existing
        private Foo foo;

        public BlackBar(Foo foo,MyDepedency dep) {
            this.foo = foo;
            this.dep = dep;
        }
    }

seems ok. Whenever, I get a BlackBar, I go find the @Existing field, and then set the value. I do not care for the value in constructor. Let's say:

    Foo foo = ...
    Bar bar = injector.getInstance(BlackBar.class);
    setExistingFieldWithValueOf(foo,bar);

The problem here is that whenever I ask injector, it sees the Foo dependency, and it is creating a new Foo in order to inject. Then, I go change its value. This will cause an infinite loop since during Foo injection which is a @ContainsBar, the process will be repeated. Even if i manage to stop the infinite loop, still the creation of a new Foo that will never be used seems a bad overhead. If there is a way to inject with null value to foo in the constructor, let me know. I will use this.

1 Answers

This might be a shot in the dark and if I'm missing something please let me know. First off, I definitely think AssistedInject is a solid choice, with a minimal footprint for each Bar.

A couple things missing from your question and comments are:

  • What are some sample annotated fields of Foo?
  • How do you expect multiple Bars to be created?
  • Are you dealing with Bar and OtherBar implementations? Or are you dealing with Bar and <T extends Bar> interface?

Based on some of these things, it might be doable with:

// BarProvider.java
public class BarProvider {
  // Get the injector, frowned upon but required
  @Inject Injector injector;

  public <T extends Bar> T get(Foo foo, String fieldname) {
    // Provider right now returns on Bar at a time, I'm guessing this is based on
    // fieldname, which can lookup the @Annotation to get the class
    Annotation annotation = ...;

    Bar bar = ...;
    // Now you need to create the bar, this can be done in a couple of ways:
    // Option 1 is to reflect the Bar(Foo) constructor 
    // then doing an injector.injectMembers(bar) to fill the remaining fields

    // Option 2 is to use field/method injection and doing a combination of 
    // Bar bar = injector.getInstance(annotatedClass); followed by
    // bar.setFoo(foo);

    return bar;
  }
}

// Foo.java
public class Foo {

}

// Bar.java
public interface Bar {

}

// ABar.java
public class ABar {
  
}

// BBar.java
public class BBar {

}

In both cases you would not @Inject the Foo field in Bar. Again this makes some assumptions on how you know which field you want to look at in Foo for the annotation class type.

Another option would be to return all of them using:

public <T extends Bar> T[] get(Foo foo) {
    // Same thing but loop through all Annotated fields
}

In both cases I'm assuming that Bar and OtherBar are actually implementations of the same interface which means you'd always be using Bar as a return type. It's still possible otherwise, just a little more annoying.

Hopefully I'm not way off base, and something might be helpful. But again AssistedInject the way it was described above would still probably be a better solution.

Edit 1 - Circular Dependencies

If you have these classes, for example, you will not have issues with Foo being injected and then overwritten.

public abstract class Bar<T> {
   T foo;

   setFoo(T foo) {
      this.foo = foo;
   }
}

public class BlackBar extends Bar<Foo> {
   @Inject Dep1 dep1;   // Injected
   @Inject Dep2 dep2;   // Injected
   
   BlackBar() {}
}

@ContainsBar
public class Foo {
    @BlackBar
    SomeObject someObject;
}

Now in your example:

Foo foo = injector.getInstance(Foo.class);   // Inject SomeObject
Bar bar = injector.getInstance(BlackBar.class);  // Inject Dep1 and Dep2
bar.setFoo(foo); // Set Foo manually

This won't cause the problem you think it does, because Foo foo is NOT annotated with @Inject therefore Guice won't create a new one when building BlackBar.

But this still doesn't answer how exactly you're USING your bars. I also have a feeling I'm missing something very important to the end goal.

Related