Generalize guice's robot-legs example with Multibinding
Asked Answered
V

1

9

I have this use case that is very similar to the robot-legs example of Guice, except I don't know how many "legs" I have. Therefore I can't use the annotations needed for the robot-legs example.

I expect to gather all these "legs" in an java.util.Set with Guice's Multibindings extension.

Technically, in a PrivateModule I would like to expose an implementation directly as an element of the set that will be provided by the Multibindings extension. I just don't know how to do that.

For reference and code example, see the robot-legs example here: http://code.google.com/p/google-guice/wiki/FrequentlyAskedQuestions#How_do_I_build_two_similar_but_slightly_different_trees_of_objec


Here's my precise use-case:

I have the following:

// Main application
public interface MyTree {...}
public interface MyInterface {
  public MyTree getMyTree() {}
}
public abstract class MyModule extends PrivateModule {}
public class MyManager {
  @Inject MyManager (Set<MyInterface> interfaces){ this.interfaces = interfaces }
}
public class MainModule extends AbstractModule {
  public void configure () {
    // Install all MyModules using java.util.ServiceLoader.
  }
}


// In expansion "square.jar"
public class SquareTree implements MyTree {...}
public class SquareImplementation implements MyInterface {
  @Inject SquareImplementation (MyTree tree) { this.tree = tree; }
  public MyTree getMyTree () { return this.tree; }
}
public class SquareModule extends MyModule { // correctly defined as a ServiceLoader's service.
  public void configure () {
    // How to make this public IN a multibinder's set?
    bind(MyInterface.class).to(SquareImplementation.class);

    // Implementation specific to the Squareimplementation.
    bind(MyTree.class).to(SquareTree.class);
  }
}

// In expansion "circle.jar"
public class CircleTree implements MyTree {...}
public class CircleImplementation implements MyInterface {
  @Inject CircleImplementation (MyTree tree) { this.tree = tree; }
  public MyTree getMyTree () { return this.tree; }
}
public class CircleModule extends MyModule { // correctly defined as a ServiceLoader's service.
  public void configure () {
    // How to make this public IN a multibinder's set?
    bind(MyInterface.class).to(CircleImplementation.class);

    // Implementation specific to the Circle implementation.
    bind(MyTree.class).to(CircleTree.class);
  }
}

Since I'm speaking about expansion jars, I don't know them at first, I don't even know how many of them there exist: I need to load the MyModules with j.u.ServiceLoader and each module should define a MyInterface implementation (these two parts are ok).

The issue is to get all the MyInterface implementations in one set (in MyManager). How can I do that?


Solution, completely based upon Jesse's answer :

// Create the set binder.
Multibinder<MyInterface> interfaceBinder = Multibinder.newSetBinder(binder(), MyInterface.class, MyBinding.class);

// Load each module that is defined as a service.
for (final MyModule module : ServiceLoader.load(MyModule.class)) {

  // Generate a key with a unique random name, so it doesn't interfere with other bindings.
  final Key<MyInterface> myKey = Key.get(MyInterface.class, Names.named(UUID.randomUUID().toString()));
  install(new PrivateModule() {
    @Override protected void configure() {
      // Install the module as part of a PrivateModule so they have full hands on their own implementation.
      install(module);
      // Bind the unique named key to the binding of MyInterface.
      bind(myKey).to(MyInterface.class);
      // Expose the unique named binding
      expose(myKey);
    }
  });
  // bind the unique named binding to the set
  interfaceBinder.addBinding().to(myKey);
}

This allows me to not force the "customer" to extend a PrivateModule, but rather use any module implementation if MyModule is an interface that extends Module.

Veliavelick answered 8/7, 2011 at 14:22 Comment(0)
H
10

It looks like you're going to need to jump through some hoops to promite bindings from the private module so that they can be in the top-level injector's multibinding.

This should work:

public class SquareModule extends AbstractModule { // does not extend PrivateModule
  @Overide public void configure() {
    // this key is unique; each module needs its own!
    final Key<MyInterface> keyToExpose
        = Key.get(MyInterface.class, Names.named("square"));

    install(new PrivateModule() {
      @Override public void configure() {

        // Your private bindings go here, including the binding for MyInterface.
        // You can install other modules here as well!
        ...

        // expose the MyInterface binding with the unique key
        bind(keyToExpose).to(MyInterface.class);
        expose(keyToExpose);
      }
    });

    // add the exposed unique key to the multibinding
    Multibinder.newSetBinder(binder(), MyInterface.class).addBinding().to(keyToExpose);
  }
}

This workaround is necessary because multibindings need to happen at the top-level injector. But private module bindings aren't visible to that injector, so you need to expose them.

Hemimorphite answered 8/7, 2011 at 15:22 Comment(6)
Thanks Jesse, however after reading your answer, I was not sure I had correctly explained the problem. So I corrected my question with an example. Is this more understandable now?Myronmyrrh
Ok, I will test your new answer and report the result here.Myronmyrrh
Yes, it works perfectly. It's quite hard to make it modularizable, but it's ok.Myronmyrrh
To make it modularizable, create your own AbstractModule subclass that includes all of the code above, plus an abstract method configurePrivateBindings(). Then call that method from within the anonymous PrivateModule's configure() method. To get a unique key for each implementation, just make a static counter that you can call toString() on.Hemimorphite
I chose the "final" field for the unique key, but basically, that's exactly the same. Thanks for the help !Myronmyrrh
I apologize for commenting on a dead thread, but @JesseWilson, your added idea won't work since if you have the extra "method" in your AbstractModule subclass it's going to create public bindings and not have access to the expose methods. I was really excited by your answer until I went to try it and realized quickly. :(Thorma

© 2022 - 2024 — McMap. All rights reserved.