IAmYourFaja
IAmYourFaja

Reputation: 56944

Guice: requesting instances, not types

Let's make the statement "All Rocks have Minerals.":

public class Mineral
{
    // Nevermind why a Mineral would have a GUID.
    // This is just to show that each Mineral instance
    // is universally-unique.
    String guid;

    @Inject
    public Mineral(String id)
    {
        guid = id;
    }
}

public class Rock
{
    private Mineral mineral;

    @Inject
    public Rock(Mineral min)
    {
        mineral = min;
    }
}

If we want 2 Rock instances, each of them configured with different Mineral instances (each with their own GUIDs):

public class RockModule extends AbstractModule
{
    public void configure(Binder binder)
    {
        // Make two Minerals with different GUIDs.
        Mineral M1 = new Mineral(UUID.getRandomId().toString());
        Mineral M2 = new Mineral(UUID.getRandomId().toString());

        // Configure two Rocks with these unique Minerals
        Rock R1 = new Rock(M1);
        Rock R2 = new Rock(M2);

        // Define bindings
        bind(Rock.class).toInstance(R1);

        // No way for Guice to expose R2 to the outside world!
    }
}

So now, when we ask Guice for a Rock, it will always give us the R1 instance, which itself is configured with the M1 instance of Mineral.

In Spring DI, you can define two beans to be the same type, but just give them different bean IDs. Then you "wire" beans together using their IDs. So I could wire R1 and M1 together, R1 and M2 together, etc. Then, I can ask Spring for R1 or R2 as I need them.

With Guice, you can only ask for the type you want (Rock.class), not the instance.

How do you request different "wired beans" with Guice? By using different AbstractModule concretions? Or is this a limitation of Guice?

Upvotes: 0

Views: 247

Answers (2)

Ryan Nelson
Ryan Nelson

Reputation: 4676

Generally this would be a violation of what Guice recommends. You would not typically new up a class inside of a module to create instances because it defeats the purpose of using a DI container in the first place. (Why then annotate your classes with @Inject?)

Rather, you'd let Guice do this for you:

class RockModule extends AbstractModule {
  public void configure() {}

  @Provides
  @Named("UUID")
  public String getUuid() {
    return UUID.getRandomId().toString();
  }
}

Now each Mineral automatically gets a unique UUID, and each Rock gets a unique Mineral.

This gets us part way, but if you want two unique pairings of a Rock and Mineral for use at runtime, then this is what Guice calls the "robot legs" problem, and you can solve it using private modules. See an example here.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1503984

I suspect you want binding annotations. Guice doesn't bind by type - it binds by key - but if you don't do anything else, the key will just consist of the type. Binding annotations allow you to create richer keys.

The linked documentation explains it better than I can, but the result is that there are various ways of specifying what you want, including:

@Inject
public Foo(@Named("X") Rock rock)

or

@Inject
public Foo(@HardRockCafe Rock rock)

where in the latter case, @HardRockCafe is a custom binding annotation.

Upvotes: 1

Related Questions