Guice无法实例化扩展JPanel的类 - NPE在调用超级构造函数时

时间:2017-02-28 10:48:47

标签: java swing nullpointerexception classloader guice

我们有一个带有Google Guice 4.1.0依赖注入的桌面Swing应用程序。在开发期间一切正常,但是当同事试图运行应用程序时发生了一些奇怪的事情。

我们有一个扩展MainWindow的{​​{1}}类。在构造函数中,此类采用一些本身可注入的控制器。在主要方法中,创建了Guice注入器。然后,注入器尝试实例化JPanelMainWindow)。它失败了injector.getInstance(MainWindow.class)

这在我的计算机上不会发生,我们使用相同的JDK。

以下NullPointerException类被剥离为有问题的代码(注意:不幸的是,这不会重现问题):

MainWindow

这是class MainWindow extends JPanel { private final Foo foo; private final JFrame frame; @Inject public MainWindow(Foo foo) { super(new GridBagLayout()); // <-- NullPointerException this.foo = foo; this.frame = new JFrame("title"); } public void createAndShowGUI() { // ... frame.add(this); frame.pack(); frame.setVisible(true); } } 方法:

main()

这是异常的堆栈跟踪:

class Main {
    private static final Injector injector = Guice.createInjector();

    public static void main(String[] args) {
        MainWindow mainWindow = injector.getInstance(MainWindow.class);

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                mainWindow.createAndShowGUI();
            }
        });
    }
}

NPE被抛到了最令人惊讶的地方 - 在调用com.google.inject.ProvisionException: Unable to provision, see the following errors: 1) Error injecting constructor, java.lang.NullPointerException at app.gui.MainWindow.<init>(MainWindow.java:133) while locating app.gui.MainWindow 1 error at com.google.inject.internal.InjectorImpl$2.get(InjectorImpl.java:1028) ~[app-1.0-SNAPSHOT.jar:?] at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1054) ~[app-1.0-SNAPSHOT.jar:?] at app.Main.createAndShowGUI(Main.java:40) ~[app-1.0-SNAPSHOT.jar:?] at app.Main.access$000(Main.java:26) ~[app-1.0-SNAPSHOT.jar:?] at app.Main$2.run(Main.java:67) ~[app-1.0-SNAPSHOT.jar:?] 超类的构造函数时(这是第133行)。我开始挖掘并发现手动创建MainWindow并注入其依赖项正常工作:

MainWindow

我怀疑类加载器可能无法正常工作,所以我再次尝试使用MainWindow mainWindow = new MainWindow(injector.getInstance(Foo.class)); MainWindow的日志类加载器:

JPanel

类加载器不同(引导程序加载System.out.println("MainWindow: " + MainWindow.class.getClassLoader()); System.out.println("JPanel: " + JPanel.class.getClassLoader()); MainWindow mainWindow = injector.getInstance(MainWindow.class); ),但现在注入工作正常。我想这是因为现在JPanel类被显式加载到main方法上下文中。

所以我的问题是:

  1. 有没有人有类似的问题?
  2. 是我的错误,还是错误?
  3. 如果它是一个bug,它会发生在Guice吗?或者也许是JRE?
  4. 有关Java和OS的更多详细信息:

    • 我最初使用JDK 1.8.0u111开发它,但后来切换到JDK 1.8.0u121。
    • 应用程序编译为Java 6.
    • 使用Windows 10,版本1607(OS Build 14393.693),在JRE 6和JRE 8(来自JDK)的计算机上运行完美无缺。
    • JPanel在同事的计算机上使用Windows 10,版本1511(OS Build 10586.753),JDK 1.8.0u112和1.8.0u121。

    不幸的是,我无法提供重现问题的最小版本。哎呀,我甚至无法重现这个问题,它只发生在同事的环境中。

1 个答案:

答案 0 :(得分:1)

我非常怀疑这是由于竞争条件造成的。 Swing组件不是线程安全的,应根据swing package javadoc在EDT上实例化:

  

Swing的线程政策

     

一般来说,Swing不是线程安全的。所有Swing组件和相关   除非另有说明,否则必须在活动中访问课程   调度线程。典型的Swing应用程序进行处理   响应从用户手势生成的事件。例如,   单击JButton会通知所有添加到的ActionListeners   JButton的。因为调度了从用户手势生成的所有事件   事件调度线程,大多数开发人员都没有受到影响   限制。

     

然而,影响在于构建和展示Swing   应用程序。调用应用程序的主要方法或方法   在事件派发线程上不调用Applet。 因此,关心   必须采取将控制转移到事件调度线程时   构建和显示应用程序或小程序。首选方式   转移控制并开始使用Swing就是使用   invokeLater的。 invokeLater方法调度Runnable   在事件调度线程上处理。

(强调我的)

现在您使用invokeLater在EDT中启动UI,但是您在主线程上构建UI(通过Guice注入器调用)。 Guice注入器调用也应该在invokeLater部分,以启动用户界面。