简体   繁体   中英

Guice: injection vs wiring

Lot's of times, classes need to be instantiated (constructed), and then "wired" (configured) before they can be used. For instance:

// Construction.
EventBus bus = new EventBus();
FizzEventHandler fizzHandler = new FizzHandler();
BuzzEventHandler buzzHandler = new BuzzHandler();

// Wiring.
bus.register(fizzHandler);
bus.register(buzzHandler);

In Guice, we accomplish the first part (construction; injection) with a Binder :

public class MyModule extends AbstractModule {
    @Override
    public void configure() {
        bind(EventBus.class).to(SimpleEventBus.class);
        bind(FizzEventHandler.class).to(DefaultFizzEventHandler.class);
        bind(BuzzEventHandler.class).to(DefaultBuzzEventHandler.class);
    }
}

But where does the wiring take place? When my Guice-based app starts up, we engage the DI "bootstrapping" process:

public class MyApp {
    private EventBus bus;
    private FizzEventHandler fizzHandler;
    // ...etc.

    public static void main(String[] args) {
        MyApp app = new MyApp();
        app.run();
    }

    public MyApp() {
        // Bootstrap DI.
        MyModule myModule = new MyModule();
        Injector injector = Guice.createInjector(myModule);

        bus = injector.inject(EventBus.class);
        fizzHandler = injector.inject(FizzEventHandler.class);
        // ...etc.

        // Wire
        bus.register(fizzHandler);
    }
}

This works OK for the top-level (root) DI classes. But as we get further "down" the dependency tree, and get into all the other objects used by the application, putting the wiring logic in constructors like this is ugly and (I believe ) is a discouraged practice.

So I ask: where doe battle-weary Guice veterans place their wiring/config code?

I work on a reasonably big system (~3000 classes) which uses Guice. I would say that our approach is to do everything with constructors. There aren't distinct "construction" and "wiring" activities as you describe, there's only construction.

In your example, the event handlers would be constructor parameters to the bus, which would register them in its constructor.

If you want to have fairly flexible injection of all the components of a given type (here, you would want to inject all event listeners into the bus), you could use multibindings . However, i don't think we actually use this in our codebase; we just write out manual lists of everything that needs injecting, which turns out not to be all that arduous in practice.

I generally use multiple modules, separated out by logical function. So one module might have authentication in it, another has data repositories, another the messaging system that I'm using, etc . This allows you to have different modules for mocking, caching Vs. non-caching, or just different implementations of the same service, and to switch out chunks of dependencies quickly and easily.

To make things even more flexible you could have a configuration file which declares the modules that should be used when the injector starts up.

When I have some logic to be done right after I instantiate my object I usually do it in methods annotated with @Provides. Your example might looks like this :

public class MyModule extends AbstractModule {
   @Override
   protected void configure() {

    bind(FizzEventHandler.class).to(DefaultFizzEventHandler.class);
    bind(BuzzEventHandler.class).to(DefaultBuzzEventHandler.class);

   }


   @Provides
   public EventBus getEventBus(SimpleEventBuss simpleBus/* this here is going to be injected as it is a class not an interface and Guice is clever and it know how to do it ;) */
     , FizzEventHandler fizz, BuzzEventHandler buzz) {
    simpleBus.register(fizz);
    simpleBus.register(buzz);
    return simpleBus;
   }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM