Reputation:
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
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