javadependency-injectionguiceassisted-inject

Managing complex life cycles in Guice


I've run into a situation where I have a graph of data objects, and would like to create one service for each node on this graph. The problem is this service (and its dependencies) are dependent on the the node they're working for. Something like this:

class Visitor {

  void enter(Node node){
    Service service = guice.create(node)

    service.doComplexDomainLogic(/*some runtime information about the graph and the path to get here, other things*/)
  }
}

Note that my intention here is to create a new instance of Service, and a new instance of any dependencies for Service, for every node on the graph.

So now I have a couple options:

We've done the second in a couple places simply because me and my team weren't aware of custom scopes. We now have one other use of a custom scope, and I must confess it was much more complex to implement than I would like. Following this guide helps, but its clear that I'm going to have to delve back into thready-safety land, and my last visit there was not so pleasant.

What guice facility should I use to get this behaviour?


Solution

  • You can inject the Injector and create a child injector for the pieces you want. A child injector will let you amend the object graph and access all of the parent injector's dependencies, but you can access your Node as deep as you'd like.

    class Visitor {
      @Inject Injector injector;
    
      void enter(final Node node) {
        Service service = injector.createChildInjector(new AbstractModule() {
          @Override public void configure() {
            bind(Node.class).toInstance(node);
            // Anything that does require a Node should be bound here, because
            // you can't define it in the parent due to the unsatisfied binding.
            bind(SomeInterface.class).to(SomeClassThatRequiresNode.class);
          }
        }).getInstance(Service.class);
    
        service.doComplexDomainLogic(/* ... */)
      }
    }
    

    Though it's possible to do something similar with scopes, bear in mind that scopes are only meant to identify when to create a new object versus when to return the same object. This means that you could create a @NodeScoped scope and ensure that the same objects would be returned while processing a given node, but you would still need to bind some kind of @NodeScoped NodeHolder to hold your nodes as you dive in. Rather than keeping and populating this separate holder, a child injector will let you ask for a Node directly, which may make your code easier to understand and test.

    See also: