Search code examples
javaguicethrift

How to access the Guice injector throughout application


I've just started using Guice and after reading the docs, skimming through a few books and watching the 2009 Google I/O talk I'm trying to convert a thrift project that relies on a few global data structures. At the moment they're created when the thrift server starts and passed to the handler on each request. They're also used in every part of the code! Essentially singletons.

As I understand it's best practice to create the injector in your main method and load all you're modules once.

I'm not sure how I'm meant to use this injector somewhere else in my code. Should I wrap it in a singleton class and litter my code with

Injector injector = InjectorInstance.get();
ClassA obj = injector.getInstance(ClassA.class);

Or is there a method I don't know about

ClassA obj = Guice.getLastInjector().getInstance(ClassA.class);

I've also found a recommendation to pass around Providers but I'm not sure how that's any better than passing the actual data structures down the call stack.

If someone could explain the recommended pattern, or even better send me in the direction of a good open source project that uses guice I would be grateful!


Solution

  • For Guice, the idea is that you have an entire graph of dependencies, each of which keeps a reference to the things it needs across its lifetime:

    class EntryPoint {
      public static void main(String[] args) {
        YourApp yourApp = Guice.createInjector(yourListOfModules())
            .getInstance(YourApp.class);
        yourApp.run();
      }
    }
    
    class YourApp {
      @Inject DependencyA dependencyA;
    }
    
    class DependencyA {
      @Inject DependencyB dependencyB;
    }
    
    class DependencyB {
      /** This is injected once, so you'll always only get the same instance. */
      @Inject DependencyC dependencyC;
      /** By injecting the provider, you can get multiple instances. */
      @Inject Provider<DependencyD> dependencyD;
    }
    

    Guice takes care of the plumbing, so YourApp and DependencyA don't need to worry about whether DependencyB needs DependencyC or DependencyD. In this sense, you never call new: Guice takes care of creating every @Injected dependency for you, and then supplying its dependencies.

    If you haven't plumbed through your dependencies (i.e. you still call new in your code), you'll no longer have access to the Injector from the manually-constructed class. In that case you may want to use getInstance or getProvider and stash it away in a static field. Guice will also help you do this with requestStaticInjection, which is not great for long-term Guice design but may help you work with legacy code or transitioning into Guice.


    Update: It may be tempting to go further, as in a static instance holder pattern:

    class InjectorHolder {
      static Injector injector;
      static {
        // WARNING: Poor practice as described below.
        injector = Guice.createInjector(new YourModule1(), ...);
      }
    }
    

    However, this takes a process known to be heavy—Guice startup can be extremely slow for extremely large graphs, especially given reflection, module evaluation, and eager Singleton initialization—and puts it all in a place where it runs at an arbitrary time on an arbitrary thread that blocks further classloading. It also implies that your Injector holder will be an effective singleton, so rather than requestStaticInjection putting Injector-powered instances locally in your arbitrary classes (already a pragmatic choice but one compatible with local declarative dependencies), you imply some other object elsewhere in your graph will access your holder to get to its Injector. This likely means your DI objects might get their dependencies from a secret global static service locator rather than expressing them declaratively in fields and constructor parameters.

    For those reasons you should use extreme caution in applying any global static holder: it might look and behave fine in a tiny graph, or might be a lemma during a full migration to better Guice practices, but Guice was meant and optimized for large enterprise DI deployment so I have to assume that's the use case for you and other future readers. If you invest in this InjectorHolder pattern, then as your graph grows you might find its associated anti-patterns to be worse for readability and predictability than many other solutions including manual DI and proper use of Guice, and the investment in the pattern may increase your migration time and cognitive overhead of working with the eventual proper pattern.