Multi-Instance Scopes in Guice

Recently I worked on a project with Deanna Surma where we needed better and more flexible scope support from Guice, so we created the project guice-multiscopes.

For those unfamiliar with dependency injection, inversion of control, and Guice, I highly recommend you explore the concepts and try it out with your next project.  I’ll continue assuming you have knowledge of the previous concepts.

Anyways, using Guice in the project was consistently awesome except for one thing:  scopes.  Guice provided the binding support, but we wanted to have multiple scope instances where we could enter and exit them at will.  There was a custom scope example, but it was far from what we needed.

So we created guice-multiscopes with the goal of allowing you to store your entire object graph in guice, with multi-instance scopes (bounded and unbounded), prescoping support, and easy ways to manipulate these scopes in your program.

Basic Usage

Check out the wiki for a full intro.

Here we’re binding a new unbounded multiscope, using the BattlestarScope as our scope annotation, Battlestar as our binding annotation, and NewBattlestar as our creation binding annotation

protected void configure() {
    Multiscopes.newBinder(binder(),
                          BattlestarScope.class,
                          Battlestar.class,
                          NewBattlestar.class);
}

Now, here’s an example of using (with a Request and Identity scope, like defined like above):

// Pretend this class handles new network requests
@Singleton
public class RequestHandler {
  private final Provider<ScopeInstance> newRequestProvider;
  private final Provider<ScopeInstance> newIdentityProvider;
  private final IdentityManager identities;
  private final RequestPipeline pipeline;

  @Inject
  public RequestHandler(@NewRequest Provider<ScopeInstance> newRequestProvider,
                        @NewIdentity Provider<ScopeInstance> newIdentityProvider,
                        IdentityManager identities,
                        RequestPipeline pipeline) {
    this.newRequestProvider = newRequestProvider;
    this.newIdentityProvider = newIdentityProvider;
    this.identities = identities;
    this.pipeline = pipeline;
  }

  public void requestReceived(RawRequest rawRequest) {
    ScopeInstance request = newRequestProvider.get();
    // the raw request is constructed elsewhere, but we can put it in our
    // scope to make it injectable (must have appropriate bindings)
    newRequestInstance.putInScope(Key.get(RawRequest.class), rawRequest);

    ScopeInstance identity = identities.get(rawRequest.getUID();
    if (identity == null) {
      identity = newIdentityProvider.get();
      identities.addIdentity(identity, rawRequest.getUID());
    }
    identity.setCurrentRequest(request);
    
    try {
      identity.enterScope();
      request.enterScope();
      pipeline.executeRequest(newRequestInstance);
    } finally {
      request.exitScope();
      identity.exitScope();
    }
  }
}

Keep in mind, all of the guice functionality with scopes applies here, so you can bind objects in your scopes, and there will be one instance of that object per instance of the multiscope.

Bounded scopes are similar, except that the instances are defined in the guice modules. So you get something similar to a multiset, but with scope instances, and each instance has it’s own binding annotation. A really nice feature of the bounded multiscopes is prescoping in the guice modules, so you can easily support plugin-style scope additions, where each scope instance can bind each required prescoped item to it’s own key. For example, if I have a Protocol bounded multiscope, and I say that I need an @HeaderMatcher String for matching the protocol header, I can specify that object as prescoped in my Protocol scope, and when someone defines a Protocol instance, say a ping protocol, they can prescope that Key (@HeaderMatcher String ) to whatever they want. Probably to “PING 1.0″, or even bind it to another key, like @PingProtocol String.

More Info

Hopefully this will be useful for others, check out the project page, wiki, and javadocs to learn more.