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 中,您需要做一些非常规的线程技巧,以便:

  1. 任何操作 UI 的 JFrame 或其他 JComponents 的代码都需要在事件调度线程 (EDT ) - 这是通过 SwingUtilities.invokeLater 完成的;和
  2. 任何会阻塞并需要一段时间才能完成的代码都需要被虹吸到它自己的 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个线程:

所以当我们去执行 Swing worker 时,Guice 还没有机会 bootstrap MyApp 实例(因此,它的缓存 属性 ).

我有什么想法(带有特定代码示例)如何让 Guice 与 Swing 的并发模型一起正常工作?

我不熟悉, 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.