Guice和Swing的并发模型看似不兼容
Guice and Swing's concurrency model are seemingly incompatible
这里是 Guice 4.0。我已经使用 Guice 几年了,并且非常喜欢它。我现在需要一个 Swing 应用程序,并且对如何设计 Guice API 来满足 Swing (恕我直言) 奇怪的 requirements/best 做法感到困惑。
通常,当我使用 Guice 时,我有一个像 MyApp
这样的主 "driver" class,我像这样连接 Guice:
// 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...
}
}
但是对于 Swing,JFrame
是 GUI 应用程序,所以对我来说,MyApp
扩展 JFrame
:
class MyApp extends JFrame {
// etc...
}
问题是,在 Swing 中,您需要做一些非常规的线程技巧,以便:
- 任何操作 UI 的
JFrame
或其他 JComponents
的代码都需要在事件调度线程 (EDT ) - 这是通过 SwingUtilities.invokeLater
完成的;和
- 任何会阻塞并需要一段时间才能完成的代码都需要被虹吸到它自己的
SwingWorker
线程中
假设我需要在应用程序启动时预热大型缓存(可能需要 30 - 60 秒)。这是我迄今为止的最佳尝试:
@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)
}
}
当我 运行 这段代码时,我在 SwingWorker
内的 appCache.warmUp()
语句上得到了一个 NPE。这是因为上面的代码实际上涉及到3个线程:
- main - 其中
main
& init
是 运行ning
- EDT - Guice bootstrapping
MyApp
实例
- Swing Worker - 其中
SwingWorker
正在 运行ning 并尝试预热缓存
所以当我们去执行 Swing worker 时,Guice 还没有机会 bootstrap MyApp
实例(因此,它的缓存 属性 ).
我有什么想法(带有特定代码示例)如何让 Guice 与 Swing 的并发模型一起正常工作?
我不熟悉guice, but you can continue any non-GUI initialization after invoking EventQueue.invokeLater()
—such code will simply run on the initial thread. An example may be seen here。当然,您必须同步对任何共享数据的访问,但您可以在初始线程结束时以通常的方式使用 EventQueue.invokeLater()
更新 GUI。您的 GUI 应在此期间阻止使用任何不完整的数据。
顺便说一句,我认为 MyApp
没有理由延长 JFrame
。相反,add()
将 Container
封闭到 JFrame
的实例中。举了几个例子here.
这里是 Guice 4.0。我已经使用 Guice 几年了,并且非常喜欢它。我现在需要一个 Swing 应用程序,并且对如何设计 Guice API 来满足 Swing (恕我直言) 奇怪的 requirements/best 做法感到困惑。
通常,当我使用 Guice 时,我有一个像 MyApp
这样的主 "driver" class,我像这样连接 Guice:
// 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...
}
}
但是对于 Swing,JFrame
是 GUI 应用程序,所以对我来说,MyApp
扩展 JFrame
:
class MyApp extends JFrame {
// etc...
}
问题是,在 Swing 中,您需要做一些非常规的线程技巧,以便:
- 任何操作 UI 的
JFrame
或其他JComponents
的代码都需要在事件调度线程 (EDT ) - 这是通过SwingUtilities.invokeLater
完成的;和 - 任何会阻塞并需要一段时间才能完成的代码都需要被虹吸到它自己的
SwingWorker
线程中
假设我需要在应用程序启动时预热大型缓存(可能需要 30 - 60 秒)。这是我迄今为止的最佳尝试:
@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)
}
}
当我 运行 这段代码时,我在 SwingWorker
内的 appCache.warmUp()
语句上得到了一个 NPE。这是因为上面的代码实际上涉及到3个线程:
- main - 其中
main
&init
是 运行ning - EDT - Guice bootstrapping
MyApp
实例 - Swing Worker - 其中
SwingWorker
正在 运行ning 并尝试预热缓存
所以当我们去执行 Swing worker 时,Guice 还没有机会 bootstrap MyApp
实例(因此,它的缓存 属性 ).
我有什么想法(带有特定代码示例)如何让 Guice 与 Swing 的并发模型一起正常工作?
我不熟悉guice, but you can continue any non-GUI initialization after invoking EventQueue.invokeLater()
—such code will simply run on the initial thread. An example may be seen here。当然,您必须同步对任何共享数据的访问,但您可以在初始线程结束时以通常的方式使用 EventQueue.invokeLater()
更新 GUI。您的 GUI 应在此期间阻止使用任何不完整的数据。
顺便说一句,我认为 MyApp
没有理由延长 JFrame
。相反,add()
将 Container
封闭到 JFrame
的实例中。举了几个例子here.