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

Arc*_*hie 8 java swing guice nullpointerexception classloader

我们有一个桌面Swing应用程序,带有Google Guice 4.1.0依赖注入.在开发过程中一切都运行良好,但是当同事试图运行应用程序时发生了一些奇怪的事情.

我们有一个MainWindow课程延伸JPanel.在构造函数中,此类采用一些本身可注入的控制器.在主要方法中,创建了Guice注入器.然后注入器尝试实例化MainWindow(injector.getInstance(MainWindow.class)).它失败了NullPointerException!

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

这里的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是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();
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这是异常的堆栈跟踪:

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:?]
Run Code Online (Sandbox Code Playgroud)

NPE被抛入最令人惊讶的地方 - 在调用超类的构造函数MainWindow(这是第133行).我开始挖掘并发现手动创建MainWindow和注入其依赖项正常工作:

MainWindow mainWindow = new MainWindow(injector.getInstance(Foo.class));
Run Code Online (Sandbox Code Playgroud)

我怀疑,也许类加载器不能正常工作,所以我既记录的类加载器试图再次MainWindowJPanel:

System.out.println("MainWindow: " + MainWindow.class.getClassLoader());
System.out.println("JPanel:     " + JPanel.class.getClassLoader());
MainWindow mainWindow = injector.getInstance(MainWindow.class);
Run Code Online (Sandbox Code Playgroud)

类加载器是不同的(JPanel由引导程序加载),但现在注入工作正常.我想这是因为现在JPanel类被显式加载到main方法上下文中.

所以我的问题是:

  1. 有没有人有类似的问题?
  2. 这是我的错误,还是一个错误?
  3. 如果它是一个bug,它会发生在Guice吗?或者也许是JRE?

有关Java和OS的更多详细信息:

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

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

bow*_*ore 1

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

Swing 的线程策略

一般来说,Swing 不是线程安全的。除非另有说明,所有 Swing 组件和相关类都必须在事件分派线程上访问。典型的 Swing 应用程序响应用户手势生成的事件进行处理。例如,单击 JButton 会通知添加到 JButton 的所有 ActionListener。由于用户手势生成的所有事件都在事件调度线程上调度,因此大多数开发人员不会受到该限制的影响。

然而,影响在于构建和显示 Swing 应用程序。对应用程序的 main 方法或 Applet 中的方法的调用不会在事件调度线程上调用。因此,在构造和显示应用程序或小程序时,必须注意将控制权转移到事件分派线程。转移控制权并开始使用 Swing 的首选方法是使用 invokeLater。invokeLater 方法安排一个 Runnable 在事件调度线程上进行处理。

(强调我的)

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