user1768830
user1768830

Reputation:

Guice: wiring a bean with complex creational pattern

I am using Guava to create an EventBus for a publish-subscribe messaging service. I am also attempting to use Guice for the first time. I've read the Guice tutorials, and have been playing around with the AbstractModule and Binder classes. But the minute I leave the tutorials and try to actually get something working for my project, I'm choking.

My project has an EventMonitor that will have a Guice-injected instance of the Guava EventBus:

public class EventMonitor {
    @Inject
    private EventBus guavaEventBus;

    // ...
}

In the Guice/DI/Bootstrapping code of my application, I define an AbstractModule concretion:

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
        // Here is where I want to wire together the EventBus to give
        // to the EventMonitor.
    }
}

Ultimately, I'd like an EventBus that would normally be constructed (in non-Guice code) like so:

ThreadFactory factory = ThreadManager.currentRequestThreadFactory();
Executor executor = Executors.newCachedThreadPool(factory)
EventBus eventBus = new AsyncEventBus(executor);

I'm choking because of the two (seemingly un-injectable) static methods on ThreadManager and Executors, and because my reference is for an EventBus but the actual object is an AsynEventBus; thus I'm not sure how to bind it:

// Doesn't work because how does Guice know I'm referencing an AsyncEventBus?!?
bind(EventBus.class).toInstance(executor);

// Doesn't work because now I've lost the ability to pass the
// AsyncEventBus an 'executor' and no-arg ctor is used!
bind(EventBus.class).to(AsyncEventBus.class);

So I ask: given the manner in which I'd like to construct my EventBus, how would a battle-worn Guice veteran wire things up here (using the ThreadFactory, Executor and EventBus) such that in EventMonitor, the fully-configured EventBus is properly injected? I think once I see this more "complex" example, I'll start to see the forest through the trees. Thanks in advance.

Upvotes: 2

Views: 1788

Answers (1)

Daniel Martin
Daniel Martin

Reputation: 23548

how would a battle-worn Guice veteran wire things up here

Two words: Provider methods.

public class MyAppModule extends AbstractModule {
    @Override
    public void configure() {
        bind(EventBus.class).to(AsyncEventBus.class);
    }

    @Provides @Singleton
    ThreadFactory providesThreadFactory() {
        return ThreadManager.currentRequestThreadFactory();
    }

    @Provides @Singleton
    Executor providesExecutor(ThreadFactory factory) {
        return Executors.newCachedThreadPool(factory)
    }

    @Provides @Singleton
    AsyncEventBus providesAsyncEventBus(Executor executor) {
        return new AsyncEventBus(executor);
    }
}

The convention of naming an @Provides method beginning with "provides" isn't something guice needs, but is something you really want to do, especially on a large codebase. Being able to search through a body of code for "provides" and find the method that provides a particular object.

A few notes to answer questions in the comments:

  • Guice examines every Module instance you install for @Provides methods, and installs them as though you'd bound the return type of the method with .toProvider to an anonymous Provider instance. So the great thing about them is that you don't need any additional code inside your configure method to get it to use them.

  • The @Singleton annotation tells guice that you only want one instance of that thing, so it'll only call the provides* method once. The default, if you leave it off, is to have guice call your provider and/or instantiate a new object (depending on what you've configured for that class) every time it needs to inject an instance. Note: some people freak out when first discovering this, and then want to put @Singleton everywhere "for efficiency" - this is an improper reaction. In reality, you really want to use @Singleton sparingly and only where it would be incorrect to have multiple different instances.

    In this case, we want to make very certain that there's only one EventBus around. So long as you aren't directly injecting Executor or ThreadFactory into any other classes, you could leave the annotations off those methods. For ThreadFactory this almost certainly won't make a difference since I think ThreadManager.currentRequestThreadFactory() returns the same instance each time it's called anyway. It would make a difference for Executor, but maybe you wanted a new Executor instance in other places where you're using it?

  • The parameters to the @Provides methods are wired up the same way as the rest of the configuration. For example, in this case I know that providesAsyncEventBus will get the Executor that was returned by providesExecutor because that's the instance guice will inject whenever it's asked for an Executor. If I had two methods like this:

    @Provides @Singleton
    Executor providesExecutor1(ThreadFactory factory) {
        return Executors.newCachedThreadPool(factory)
    }
    
    @Provides @Singleton
    Executor providesExecutor2(ThreadFactory factory) {
        return Executors.newScheduledThreadPool(10, factory)
    }
    

    Then guice would throw a configuration error when you tried creating the injector, because you've told guice to use two different ways to get an Executor. Now, if instead I had something like this:

    @Provides @Singleton
    ExecutorService providesExecutor1(ThreadFactory factory) {
        return Executors.newCachedThreadPool(factory)
    }
    
    @Provides @Singleton
    ScheduledExecutorService providesExecutor2(ThreadFactory factory) {
        return Executors.newScheduledThreadPool(10, factory)
    }
    

    but still had the providesAsyncEventBus method as above, then guice would throw an error when you tried creating the injector because you didn't tell it how to create the Executor that providesAsyncEventBus requires. You'd need an additional line like:

    bind(Executor.class).to(ExecutorService.class);
    

    in your configure method to resolve the issue.

  • As for your register methods, you have a few options. What I believe is by far the easiest way to go is to just add a EventBus parameter to the @Inject-annotated constructor of your objects that need to get registered and then do evtBus.register(this) as the last line of the constructor.

    Other options include using static injection (which you can read about, but I wouldn't recommend), or binding a Set<Object> with the appropriate annotation using multibinders and then in the same startup code where you create the Injector, iterate over that set to register anything. (This second method can be done in some nice patterns, but I wouldn't recommend it until you learn more about simpler guice usage patterns)

Upvotes: 6

Related Questions