我们有一个带有Google Guice 4.1.0依赖注入的桌面Swing应用程序。在开发期间一切正常,但是当同事试图运行应用程序时发生了一些奇怪的事情。
我们有一个扩展MainWindow
的{{1}}类。在构造函数中,此类采用一些本身可注入的控制器。在主要方法中,创建了Guice注入器。然后,注入器尝试实例化JPanel
(MainWindow
)。它失败了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方法上下文中。
所以我的问题是:
有关Java和OS的更多详细信息:
JPanel
在同事的计算机上使用Windows 10,版本1511(OS Build 10586.753),JDK 1.8.0u112和1.8.0u121。不幸的是,我无法提供重现问题的最小版本。哎呀,我甚至无法重现这个问题,它只发生在同事的环境中。
答案 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
部分,以启动用户界面。