Guice 4.0 here. I've been using Guice for a few years now and really love it. I now have a need for a Swing app and am baffled at how to contrive the Guice API to meet Swing's (IMHO) bizarre requirements/best practices.
Typically, when I use Guice, I have a main "driver" class like MyApp
which I wire with Guice like so:
// Groovy pseudo-code
class MyApp {
@Inject
FizzClient fizzClient
@Inject
BuzzClien buzzClient
static void main(String[] args) {
MyApp myapp = Guice.createInjector(new MyAppModule()).getInstance(MyApp)
myapp.run()
}
private void run() {
// Do whatever the app does...
}
}
class MyAppModule extends AbstractModule {
@Override
void configure() {
bind(FizzClient).to(DefaultFizzClient)
bind(BuzzClient).to(DefaultBuzzClient)
// etc...
}
}
But in the case of Swing, the JFrame
is the GUI application, and so to me, it makes sense to have MyApp
extend JFrame
:
class MyApp extends JFrame {
// etc...
}
The problem is that, in Swing, you need to do some unconventional thread trickery so that:
- Any code that manipulates the
JFrame
or the otherJComponents
of the UI needs to happen on the Event Dispatching Thread (EDT) - this is accomplished viaSwingUtilities.invokeLater
; and - Any code that will block and take a while to finish needs to be siphoned off into its own
SwingWorker
thread
So let's say I have a need to warm up a large cache at app startup (perhaps this takes 30 - 60 seconds). Here's my best attempt thus far:
@Canonical // Creates a tuple constructor and a bunch of other stuff for me
@Slf4j
class MyApp extends JFrame {
@Inject
MyAppCache appCache
static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
void run() {
// The Guice-based bootstrapping has to happen inside invokeLater
// because MyApp *is* a JFrame (hence UI).
Guice.createInjector(new MyAppModule()).getInsance(MyApp).init()
}
})
}
void init() {
new SwingWorker<Void,String>() {
@Override
protected Void doInBackground() {
appCache.warmUp()
publish('Cached is warmed up!')
}
@Override
protected void process(List<String> updates) {
log.info(updates)
}
}.execute()
}
}
class MyAppModule extends AbstractModule {
@Override
void configure() {
bind(MyAppCache).to(DefaultAppCache)
}
@Provides
MyApp providesMyApp(MyAppCache appCache) {
MyApp app = new MyApp(appCache)
app.title = 'My App'
app.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
app.pack()
// ...etc. (configure the parent JFrame)
}
}
When I run this code I get a NPE on the appCache.warmUp()
statement inside the SwingWorker
. This is because the above code actually involves 3 threads:
- main - where
main
&init
are running - EDT - where Guice is bootstrapping the
MyApp
instance - Swing Worker - where the
SwingWorker
is running and attempting to warmup the cache
And so by the time we go to execute the Swing worker, Guice hasn't had a chance to bootstrap the MyApp
instance yet (and thus, its cache property).
Ay ideas at all (w/ specific code example) how I can get Guice working properly with Swing's concurrency model?