Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generalize guice's robot-legs example with Multibinding

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.

like image 434
Olivier Grégoire Avatar asked Jul 08 '11 14:07

Olivier Grégoire


1 Answers

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.

like image 114
Jesse Wilson Avatar answered Nov 15 '22 21:11

Jesse Wilson