Java-Programm nur einmal starten: Unterschied zwischen den Versionen
K (→Implementieren des Clients) |
(→Definition des Remote-Interfaces) |
||
Zeile 141: | Zeile 141: | ||
Die erste Methode wird also zur Ermittelung eines laufenden Servers benötigt, die Zweite, um die laufende (erste) Instanz sichtbar zu machen. | Die erste Methode wird also zur Ermittelung eines laufenden Servers benötigt, die Zweite, um die laufende (erste) Instanz sichtbar zu machen. | ||
+ | |||
+ | ==Implementierung der Remote-Funktionen== | ||
+ | |||
+ | <code=java> | ||
+ | import java.awt.Window; | ||
+ | import java.rmi.RemoteException; | ||
+ | |||
+ | public class RemoteTaskImpl implements RemoteTask { | ||
+ | private final Window window; | ||
+ | |||
+ | public RemoteTaskImpl(Window window) { | ||
+ | this.window = window; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @see RemoteTask | ||
+ | * @return | ||
+ | * @throws RemoteException | ||
+ | */ | ||
+ | @Override | ||
+ | public boolean isRunningAnotherInstance() throws RemoteException { | ||
+ | System.out.println("Another instance is already running."); | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * @see RemoteTask | ||
+ | * @throws RemoteException | ||
+ | */ | ||
+ | @Override | ||
+ | public void showRunningInstance() throws RemoteException { | ||
+ | System.out.println("The instance that is already running is displayed."); | ||
+ | window.setLocationRelativeTo(null); | ||
+ | window.setVisible(true); | ||
+ | window.toFront(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | </code=java> | ||
==Implementieren des Servers== | ==Implementieren des Servers== |
Version vom 5. August 2017, 16:12 Uhr
Dieser Beitrag wird derzeit noch bearbeitet. Der Text ist deshalb unvollständig und kann Fehler oder ungeprüfte Aussagen enthalten. |
Inhaltsverzeichnis
Einleitung
Unter bestimmten Umständen kann es gewünscht sein, dass nur eine aktive Instanz eines Programms auf einem Rechner ausführbar ist. Sollte das Programm ein weiteres Mal aufgerufen werden, soll die gerade aktive, unsichtbare Instanz des Programms in den Fokus des Bedieners geholt werden. Es stellt sich also die Frage: Wie kann überprüft werden, ob ein Programm schon läuft und dann das zweite Starten verhindert werden?
Jedes Java-Programm wird in einer eigenen virtuellen Maschine (VM) gestartet, somit sind sie räumlich voneinander getrennt.
Im Web sind einige interessante Vorschläge zu finden, die beschreiben, wie man ein Java-Programm so programmiert, dass nur eine aktive Instanz im Arbeitsspeicher zugelassen wird, man also ein Java-Programm nur "einmal" ausführen kann.
Möglichkeiten zur Lösung gibt es. Hier einige Ansätze:
- eine Lock-Datei
- einen Port sperren
- Client/Server-Anwendung
- eine Kombination aus alle dem.
Jede der Lösungen bietet Vorteile, wie auch Nachteile in der Praxis. In unserem Artikel Java-Anwendung nur einmal ausführen - Java-Blog-Buch wurden die wichtigsten beschrieben.
Demnach ist einer der größten Nachteile beim Einsatz von Lockdateien, dass man mit einer bereits gestarteten Anwendung nicht kommunizieren kann. Man kann die laufende Instanz bspw. nicht sichtbar machen, wenn sie gerade verdeckt ist. Das Gleiche trifft auf die Port-Sperrung zu. Client-/Server-Anwendungen sind meist etwas komplexer, können aber derartige Probleme lösen.
Eine bisher nicht besprochene Lösung wird im Folgenden beschrieben. Eine Lösung mit Hilfe der seit dem JDK 1.0 mitgelieferten Java-Bibliothek für verteilte Anwendungen - RMI. Sie besticht durch ihre Einfachheit gegenüber einer Client-/Server-Anwendung und einem Maximum an Vorteilen.
RMI - eine Einführung
Um die in diesem Artikel besprochene Lösung zu programmieren, werden keine RMI-Kenntnisse vorausgesetzt, sie werden aber beim Verstehen des Codes und der Vorgehensweise helfen, so dass später die Implementierung in eigenen Programmen leichter fällt.
Daher empfehlen wir, bevor wir weiter machen, den kurz und knapp gehaltenen Einführungsartikel (RMI minimal) zu studieren.
Schritt für Schritt zum Start-Limit
Beginnen wir also nun, eine kleine Demo-Anwendung zu programmieren, mit dem Ziel, dass diese nur einmal gestartet werden kann. Weitere Starts werden nur die bereits aktive Anwendung in den Fokus des Benutzers holen.
Wenn man es genau nimmt, wird zwar eine weitere Instanz der Anwendung gestartet werden können, jedoch nach der Kommunikation mit der bereits laufenden Instanz sofort wieder beendet.
Die Ausgangslage - ein einfaches Fenster
Zunächst möchten wir eine einfache Anwendung erzeugen, die wir dann im weiteren Schritt mit den gewünschten Funktionen ausbauen. <code=java> import java.awt.*; import java.awt.event.*; import javax.swing.*;
/**
* DemoFrame ist der Einstiegspunkt in die Anwendung. * Hier werden wichtige Prüfungen angestoßen und die Benutzeroberfläche zusammengebaut. * @author Gernot Segieth */
public class DemoFrame {
public DemoFrame() { JFrame frame = new JFrame("Demo-Frame"); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { frame.dispose(); } });
frame.add(createMainPanel()); frame.pack(); frame.setLocationByPlatform(true); frame.setVisible(true); }
private JPanel createMainPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setPreferredSize(new Dimension(600, 400)); return panel; }
public static void main(String[] args) { try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception ex) {
System.out.println(ex);
}
SwingUtilities.invokeLater(new Runnable() { public void run() { new DemoFrame(); } }); }
} </code=java>
Der Code der Anwendung ist schon für den nächsten Schritt vorbereitet, so dass in den folgenden Schritten weniger verwirrende Code-Änderungen durchgeführt werden müssen.
Wir erzeugen also einen leeren JFrame
.
In Zeile 8 schalten wir das Standardverhalten des JFrames beim Beenden (Klick auf den Schließen-Button des Fensters) aus. Damit der JFrame geschlossen werden kann, implementieren wir anschließend (Zeile 9 bis Zeile 13) einen WindowListener
, der für die Beendigung des Programms sorgt. Man könnte das auch auch einfacher lösen, in dem man in Zeile 8 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
schreiben würde. Aber wie erwähnt wird bereits ein später benötigtes Verhalten hier implementiert.
In Zeile 15 fügen wir das in Zeile 21 bis Zeile 25 definierte JPanel
in dem JFrame ein. Das JPanel sorgt hier erst mal nur für die Größe des JFrames bei der Ausgabe auf dem Bildschirm. Später werden wir noch Code für das JPanel hinzufügen, der die Demo anschaulicher machen wird.
In Zeile 17 legen wir fest, dass die JVM unser Fenster dort auf dem Bildschirm positionieren wird, wie es den Regeln des Host-Systems zur Ausgabe von neuen Fenstern auf dem Bildschirm entspricht.
Vorüberlegung
Gut, eine einfache GUI wurde aufgebaut. Später werden wir noch einige Objekte darin ablegen/einfügen, um eine sinnvollere Anwendung zu erhalten.
Folgende Frage müssen wir uns nun beantworten: Wie muss sich ein Programm verhalten, um zu ermitteln, ob ein gleichartiges Java-Programm bereits ausgeführt wird?
1. Wird das Programm das "erste" Mal gestartet, muss es zunächst nach einer weiteren aktiven gleichartigen Anwendung "suchen".
2. Sollte es keine aktive gleichartige Anwendung geben, ist die soeben gestartete Instanz die erste und wird dem Benutzer auf dem Bildschirm angezeigt.
3. Ist allerdings bereits eine gleichartige Anwendung aktiv, muss sich die zuletzt gestartete Instanz wieder beenden und die möglicherweise gerade nicht sichtbare erste Instanz in den Vordergrund des Bildschirms gebracht werden.
Punkt 1 klingt etwas nach dem klassischen Verhalten eines Clients. Er sucht nach einem Server, um seine Dienste zu nutzen.
Punkt 2 klingt nach dem Verhalten eines Servers. Er wird gestartet und bietet Dienste für Clients an, die sich mit ihm verbinden.
Punkt 3 klingt nach einer Interaktion von Client und Server. Der Client hat einen Server "gefunden", nutzt einen Dienst (zeige die Benutzeroberfläche an) und beendet die Verbindung bzw. sich selbst.
Das bedeutet, wir benötigen in unserem Programm das Verhalten eines Servers und eines Clients.
Und genau dieses Verhalten werden wir nun schrittweise mit Hilfe von RMI implementieren.
Definition des Remote-Interfaces
Hier nun also die Definition unseres Remote-Interfaces, welches alle Methoden enthält, die unser Programm für das gewünschte Verhalten benötigt.
<code=java> import java.rmi.Remote; import java.rmi.RemoteException;
/**
* Das Interface beschreibt die benötigten Methoden, die von einem RemoteTask implementiert werden müssen. * @author Gernot Segieth */
public interface RemoteTask extends Remote {
/** * Wird prüfen, ob bereits ein RMI-Server online ist. * Der RMI-Server ist online, wenn bspw. der RMI-Port (1099) belegt ist, ein Remote-Objekt * in der RMI-Registry gespeichert wurde und eine Methode dieses Objektes aufgerufen werden kann. * @returntrue
, wenn festgestellt werden konnte, dass der RMI-Server bereits online ist, * sonstfalse
. * @throws java.rmi.RemoteException */ boolean isRunningAnotherInstance() throws RemoteException; /** * Wird die Benutzeroberfläche der bereits laufenden Programminstanz anzeigen. * @throws java.rmi.RemoteException */ void showRunningInstance() throws RemoteException;
} </code=java>
Die erste Methode wird also zur Ermittelung eines laufenden Servers benötigt, die Zweite, um die laufende (erste) Instanz sichtbar zu machen.
Implementierung der Remote-Funktionen
<code=java> import java.awt.Window; import java.rmi.RemoteException;
public class RemoteTaskImpl implements RemoteTask {
private final Window window; public RemoteTaskImpl(Window window) { this.window = window; } /** * @see RemoteTask * @return * @throws RemoteException */ @Override public boolean isRunningAnotherInstance() throws RemoteException { System.out.println("Another instance is already running."); return true; }
/** * @see RemoteTask * @throws RemoteException */ @Override public void showRunningInstance() throws RemoteException { System.out.println("The instance that is already running is displayed."); window.setLocationRelativeTo(null); window.setVisible(true); window.toFront(); }
}
</code=java>
Implementieren des Servers
<code=java> import java.awt.Frame; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject;
/**
* Server ist der RMI-Server, der Remote-Objekte in der RMI-Registry speichert * und Anfragen von Clients beantwortet. * Die Klasse erwartet mindestens die Instanz der Fenster-Klasse, das als Single-Fenster behandelt werden soll. * * @author Gernot Segieth */
public class Server {
private RemoteTaskImpl task; /** * Erzeugt einen RMI-Server. * Die übergebene Fenster-Instanz wird bei Bedarf sichtbar gemacht. * @param frame Die Instanz der Fenster-Klasse, die sichtbar gemacht werden soll. * @throws RemoteException * @throws AlreadyBoundException */ public Server(Frame frame) throws RemoteException, AlreadyBoundException { this(frame, Registry.REGISTRY_PORT); }
public Server(Frame frame, int port) throws RemoteException, AlreadyBoundException { LocateRegistry.createRegistry(port);
task = new RemoteTaskImpl(frame); RemoteTask stub = (RemoteTask) UnicastRemoteObject.exportObject(task, 0); //RemoteServer.setLog(System.out);
Registry registry = LocateRegistry.getRegistry(); registry.bind(frame.getTitle(), stub);
System.out.println("Remote object stored in RMI registry."); }
/** public static void main(String[] args) throws RemoteException, AlreadyBoundException { Server server = server = new Server("rmi://localhost/SingleWindow"); } */
}
</code=java>
Implementieren des Clients
<code=java> import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.logging.Level; import java.util.logging.Logger;
/**
* Client ist der RMI-Client, der nach passenden Remote-Objekten in der RMI-Registry sucht * und die bereit gestellten Methoden für Anfragen an den Server aufruft. * Die Klasse erwartet mindestens die URI (Adresse), unter der nach Remote-Objekten gesucht werden soll. * @author Gernot Segieth */
public class Client {
private RemoteTask task;
public Client(String taskName) throws RemoteException { this(taskName, Registry.REGISTRY_PORT); }
public Client(String taskName, int port) throws RemoteException { try { Registry registry = LocateRegistry.getRegistry(port); task = (RemoteTask) registry.lookup(taskName); } catch (RemoteException | NotBoundException ex) { Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex); } }
/** * Ermittelt, ob bereits ein anderer RMI-Server online ist. * * @returntrue
, wenn festgestellt werden konnte, dass bereits * eine RMIRegistry bzw. ein RMI-Server gestartet wurde, sonstfalse
. * @throws RemoteException */ public boolean isRunningAnotherInstance() throws RemoteException { return task.isRunningAnotherInstance(); } public void showRunningInstance() throws RemoteException { task.showRunningInstance(); }
/** public static void main(String[] args) throws RemoteException { Client client = new Client("rmi://localhost/SingleWindow"); } */
}
</code=java>
Zusammenspiel Server und Client
Ausbau der Anwendung
Testen der fertigen Anwendung
Fragen
Das Thema wurde nicht ausreichend behandelt? Du hast Fragen dazu und brauchst weitere Informationen? Lass Dir von uns helfen!
- Besuche uns im Byte-Welt-Forum
- Besuche unseren Chat
Wir helfen dir gerne!
Dir hat dieser Artikel gefallen? Oder Du hast Fehler entdeckt und möchtest zur Berichtigung beitragen? Prima! Schreibe einen Kommentar!
Du musst angemeldet sein, um einen Kommentar abzugeben.