RMIの概要:Java言語でリモートメソッドを呼び出すためのプログラミングインターフェイス( ソース )。 たとえば、1つまたは複数のコンピューターからサーバープログラム上のデータを管理できます。 Habréで詳細を読むことができます 。 その基本にすでに精通しているという事実から始めます。 また、Java 8の革新を認識している必要があります。つまり、ラムダ式が必要になります。 ここには良い説明があります 。
RMIを使用する可能性は非常に多様です。 これを使用して、たとえば、チャットまたはJavaで投票するためのプログラムを作成できます。 私の例では、次のようなグラフィカルシェルを備えたシンプルなカウンターがあります。
- 現在のカウンター値でJLable
- JButton "Plus"は、カウンターの値を1つ上げます
- JButton「リセット」はカウンター値を1にリセットします
ただし、GUIに進む前に、RMIオブジェクト自体、カウンター、およびオブジェクトが保存されるRMIサーバーを作成します。
カウンター-カウンターインターフェイス:
import java.rmi.Remote; import java.rmi.RemoteException; public interface Counter extends Remote { final String NAME = "Counter"; int reset() throws RemoteException; int increment() throws RemoteException; }
クラス初期化カウンター「CounterClass」:
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class CounterClass extends UnicastRemoteObject implements Counter { private static final long serialVersionUID = 1L; private int counter; public CounterClass() throws RemoteException { } @Override public synchronized int reset() { this.counter = 0; return this.counter; } @Override public synchronized int increment() { this.counter++; return this.counter; } }
RMIカウンター「Counter_Server」のサーバー:
import java.io.IOException; import java.rmi.AlreadyBoundException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Counter_Server { public static void main(final String[] args) throws IOException, AlreadyBoundException { CounterClass counter = new CounterClass(); Registry localReg = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); localReg.bind(Counter.NAME, counter); System.out.println("Counter-Server runs"); } }
読者はすでにRMIに精通していることを前提としているため、これらのクラスを1行ずつ説明することはしません。 簡単な説明:リセットメソッドはカウンタ変数を0に等しくして返し、インクリメントメソッドはカウンタ変数を1だけ増やして返します。 サーバーで、CounterClassスケルトンを使用してレジスタを作成します。 その後、サーバーはすでに起動できます。
最後に、チャートに進みます。 クラスCounter_Client_GUIを作成してみましょう。このクラスはGUIでフレーム自体を作成し、同時にmainメソッドを介して以前に作成したレジスタからカウンターのリモートコントロールのスタブを取得します。
import Counter.Counter; public class Counter_Client_GUI extends JFrame { private static final long serialVersionUID = 1L; protected Counter counter; protected JLabel counterLabel; public Counter_Client_GUI(final Counter counter) { this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.counter = counter; this.counterLabel = new JLabel("0", SwingConstants.CENTER); final JButton incrementButton = new JButton(""); final JButton resetButton = new JButton(""); incrementButton.addActionListener(this::incrementClicked); resetButton.addActionListener(this::resetClicked); this.setLayout(new GridLayout(0, 1)); this.add(this.counterLabel); this.add(incrementButton); this.add(resetButton); this.setSize(300, 200); this.setVisible(true); } public static void main(String[] args) throws RemoteException, NotBoundException { Registry reg = LocateRegistry.getRegistry("localhost"); Counter counter = (Counter) reg.lookup(Counter.NAME); new Counter_Client_GUI(counter); }
説明するいくつかの行が既にあります。
- incrementButton.addActionListener(this :: incrementClicked)-ラムダ式、リスナー本体は同じクラスのincrementClickedメソッドで記述されます。
- resetButton.addActionListener(this :: resetClicked)-ラムダ式、リスナー本体は同じクラスのresetClickedメソッドで記述されます。
- レジストリreg = LocateRegistry.getRegistry( "localhost")-この例では、サーバーとクライアントの両方が同じコンピューター上にあるため、レジスタ参照の代わりに "localhost"を指定します。
次のステップの前に、この場合に並列アプローチが必要な理由を理解する必要があります。 最も一般的な方法でJLable更新を実装する場合、たとえば:
this.counterLabel.setText(String.valueOf(novoeZnacheniePeremennoiCounter));
これにより、フレームが絶えずフリーズする可能性が非常に高く、同時に単一のボタンを長時間押すことはできませんが、クリックが記録され、フレームが解凍されると、それらが1つずつ実行され、混乱が生じます。 これは、この場合、グラフィカルシェルでのすべてのアクションが1つのThread- EventDispatchThreadのみによって実行されるという事実によるものです。 この例では、クライアントとサーバーが同じコンピューター上にあり、カウンターは依然としてリモートで制御されていることを忘れないでください。そのため、RMIレジスターで障害が発生したり、サーバーへのコマンドの配信が遅れたりする可能性があります(さらに、これは単なる例ですが、実際にはクライアントとサーバーのプログラムは、もちろんローカルホストにはありません)。
ここで最も重要な部分に取りかかりましょう-incrementClickedメソッドとresetClickedメソッドについて説明し、必要な並列処理を導入します。
protected void incrementClicked(final ActionEvent ev) { new Thread(this::incrementOnGUI).start(); } protected void resetClicked(final ActionEvent ev) { new Thread(this::resetOnGUI).start(); }
説明:ボタンをクリックするたびに、新しいスレッドを作成して開始します。
各スレッド内には次のものがあります。
protected void incrementOnGUI() { try { final int doAndGetIncrement= this.counter.increment(); final String newLabelText = String.valueOf(doAndGetIncrement); EventQueue.invokeLater(() -> this.counterLabel.setText(newLabelText)); } catch (final RemoteException re) { final String message = "Fehler: " + re.getMessage(); EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(this, message)); } } protected void resetOnGUI() { try { final int doAndGetReset= this.counter.reset(); final String newLabelText = String.valueOf(doAndGetReset); EventQueue.invokeLater(() -> this.counterLabel.setText(newLabelText)); } catch (final RemoteException re) { final String message = "Fehler: " + re.getMessage(); EventQueue.invokeLater(() -> JOptionPane.showMessageDialog(this, message)); } }
EventQueue.invokeLater(...)は、プログラムの重要なポイントです。 英語の「イベントキュー」のEventQueueは、(Javaに含まれる)現在のスレッドのタスクをメインスレッドで実行するためにキューに送信する関数です。 私たちの場合、タスクはカウンター
this.counterLabel.setText(newLabelText)
を更新するか、エラーメッセージ
JOptionPane.showMessageDialog(this, message)
を表示することです。 これは、作成された多くのスレッドの作業間の混乱を避けるために必要です。 たとえば、メソッドはテーブル内の1つのスレッドの行数をカウントし、別のスレッドは行を削除します。 高い確率で、結果の数値は不正確になります。 最終的に、EventQueueには、グラフィカルインターフェイスでの他の作業を妨げることなく、順番に、または可用性によって実行されるタスクのリストが含まれます。
EventQueue.invokeLater(...)の本文にはラムダ式もあることに注意してください。 このプログラムでは、コードの外観をより良く理解しやすくするために使用されます。
以上です。 RMIを使用している場合でも、プログラムのグラフィカルシェルとの並列性が提供されます。
ご清聴ありがとうございました!