2017-02-28 14 views
8

Google Guice 4.1.0依存性注入を使用したデスクトップのSwingアプリケーションがあります。開発中はすべて正常に機能しましたが、同僚がアプリケーションを実行しようとしたときに何か不思議なことが起きました。GuiceはJPanelを拡張するクラスをスーパーコンストラクタへの呼び出しでインスタンス化できません

JPanelを拡張するMainWindowクラスがあります。コンストラクタでは、このクラスはそれ自身が注入可能ないくつかのコントローラを取ります。主な方法ではGuiceインジェクタが作成されます。次に、インジェクタはMainWindowinjector.getInstance(MainWindow.class))をインスタンス化しようとします。そして、それはNullPointerExceptionで失敗しました!

これは私のコンピュータでは起こりません。同じJDKを使用しています。ここで

問題のあるコード(注:これは、残念ながら、問題を再現しません):まで剥奪MainWindowクラスがある

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(); 
      } 
     }); 
    } 
} 

ここでトレーススタックです:

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()方法があります例外:

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:?] 

NPE最も驚くべき場所、すなわちMainWindow(これは133行目)のスーパークラスのコンストラクタの呼び出しでスローされました。私は掘り始め、マニュアルMainWindowの作成とその依存関係を注入することが正常に動作することが判明:

MainWindow mainWindow = new MainWindow(injector.getInstance(Foo.class)); 

私は多分、クラスローダが正常に動作しなかったことを疑われたので、私はMainWindowJPanelの両方のログクラスローダで再び試してみました:

System.out.println("MainWindow: " + MainWindow.class.getClassLoader()); 
System.out.println("JPanel:  " + JPanel.class.getClassLoader()); 
MainWindow mainWindow = injector.getInstance(MainWindow.class); 

クラスローダーは(JPanelは、ブートストラップによってロードされている)異なっているが、今の注入は正常に働いていました。これは、JPanelクラスがメインメソッドコンテキストに明示的にロードされたためです。

だから私の質問は以下のとおりです。

  1. 誰でも同様の問題がありましたか?
  2. 私の間違いですか、それともバグですか?
  3. これはバグの場合、Guiceで発生しますか?あるいはJRE?

よりJavaとOSの詳細:

  • 私はもともとJDK 1.8.0u111でそれを開発したが、その後、JDKの1.8.0u121に切り替えます。
  • アプリケーションはJava 6にコンパイルされます。
  • JRE 6およびJRE 8(JDK)のWindows 10、バージョン1607(OSビルド14393.693)で、自分のコンピュータで完璧に動作します。
  • NullPointerExceptionは、Windows 10、バージョン1511(OSビルド10586.753)、JDK 1.8.0u112、および1.8.0u121の同僚のコンピュータで生成されます。

残念ながら、私は問題を再現する最小限のバージョンを提供できませんでした。ヘック、私は問題を再現することさえできません、それは同僚の環境でのみ起こります。

+0

Guiceの設定ファイルを調べましたか?あなたと同僚の間に違いはありますか? –

+0

@ M.Prokhorov違いはありません。プロジェクトはMavenから構築され、追加の設定は追加されません。さらに、実行時の失敗であり、コンパイルではありません。まったく同じJARが自分のコンピュータ上で動作しますが、彼のコンピュータでは動作しません。それが私か彼によって集められたかどうかは関係ありません。 – Archie

+0

さて、クラスローダーが異なる場合は、実行時の問題です。両方のマシンで、そのログステートメントによって異なるクラスローダーが報告されていますか?インジェクタが呼び出されている間に何か印刷されていますか? –

答えて

1

これは、競合状態によるものと思われます。 Swingコンポーネントはスレッドセーフではありませんし、swing package javadocに従ってEDT上でインスタンス化する必要があります。

Swingのスレッドポリシー

一般的に、Swingはスレッドセーフではありません。特に記載がない限り、すべてのSwingコンポーネントおよび関連する クラスは、イベント ディスパッチスレッドでアクセスする必要があります。典型的なSwingアプリケーションは、ユーザーのジェスチャーから生成されたイベントへの応答で処理を行います( )。たとえば、 JButtonをクリックすると、 JButtonに追加されたすべてのActionListenerが通知されます。ユーザージェスチャーから生成されたすべてのイベントは イベントディスパッチスレッドで送出されるため、ほとんどの開発者は の制限の影響を受けません。

ただし、影響があるのは、Swing アプリケーションを構築して表示することです。アプリケーションのメインメソッドへの呼び出し、または アプレットのメソッドは、イベントディスパッチスレッドで呼び出されません。 したがって、 アプリケーションまたはアプレットを構築して表示するときには、 を注意してイベントディスパッチスレッドに制御を移す必要があります。 コントロールを転送してSwingを使用するのが好ましい方法は、 invokeLaterを使用することです。 invokeLaterメソッドは、イベントディスパッチスレッドで処理されたにランナブルをスケジューリングします。

(強調鉱山)

今、あなたは、しかし、あなたは(Guiceのインジェクタコールを介して)、メインスレッド上でUIを構築し、invokeLaterを使用して、EDTでUIを起動します。 Guiceインジェクタコールは、UIを開始するためにinvokeLater部分にも含まれている必要があります。

関連する問題