Java als Service unter Windows

4. November 2011 von Dirk Dittmar [permalink]

Um ein Java-Programm als Service unter Windows zu betreiben benötigt man einen Service-Wrapper (da führt kein Weg dran vorbei). Unter Linux gibt es auch Service-Wrapper aber dort kann man Java auch direkt als Daemon ausführen. Unter Linux habe ich mir bis jetzt immer ein passendes init.d Script runtergeladen und das leicht angepasst. Linux ist da eben (wie immer) einen Schritt weiter. Aber manchmal kommt man eben nicht daran vorbei einen Service unter Windows laufen zu lassen. *seufz*

Für Windows hab ich auf die schnelle zwei Wrapper gefunden:

Da Procrun von Apache ist und wohl auch vom Tomcat verwendet wird habe ich mich entschlossen das Tool zu nutzen.

Procrun besteht aus zwei Programmen:

  1. Prunmgr (Procrun monitor application) ist eine GUI um Procrun Dienste zu konfigurieren.
  2. Prunsrv (Procrun service application) ist der eigentliche Service-Wrapper

Die Monitor-Applikation ist nicht so interessant deshalb betrachten wir hier nur den Wrapper. Der Wrapper kann in drei Modes laufen:

  1. Im Mode jvm wird direkt die jvm.dll verwendet um das Java-Programm zum Service zu machen. Das gewünschte Java-Programm läuft so in dem Prozess des Wrappers.
  2. Im Mode exe kann eine beliebige anderes (native) Programm als Service gestartet werden. Das Programm wird dabei in einem eigenen Prozess gestartet.
  3. Der Mode java ähnelt sehr dem Mode exe nur das hier automatisch die java.exe in einem eigenen Prozess gestartet wird.

Ich finde eigentlich den Mode jvm am besten. Die Idee das Java-Programm in dem Prozess laufen zu lassen finde ich eigentlich recht charmant und die Implementierung kann man leicht so gestalten das mein init.d Ansatz für Linux mit dem gleichen Source-Code funktioniert.

Um unser Beispiel-Programm als Service zu installieren lädt man sich die Binaries von der Apache-Seite herunter. Dann braucht ihr natürlich auch meine Sample-App (ist Eclipse-Projekt). Dann entpackt ihr das Sampel und entpackt die Distribution von Apache so das die prunsrv in dem Verzeichnis liegt in das ihr mein Beispiel entpackt habt. Dann könnt ihr mit dem Batch-Script (service_install.bat) den Service installieren:

@ECHO OFF
%~dp0\prunsrv //IS//SimpleService  ^
	--Jvm=auto --StartMode=jvm --StopMode=jvm  ^
	--Classpath %~dp0\SimpleService.jar  ^
	--StartClass de.wortzwei.simpleService.Service --StartParams start  ^
	--StopClass de.wortzwei.simpleService.Service --StopParams stop  ^
	--StartPath %~dp0 --LogPath %~dp0

Der Inhalt ist leicht erklärt (genauer könnt ihr das in der Dokumentation nachlesen): Es wird ein Service mit dem Namen “SimpleService” installiert. Die Jvm wird automatisch bestimmt und das Starten bzw. Stoppen passiert im Modus jvm. Natürlich muss der Classpath, das Working-Directory (StartPath) und der LogPath (hier schreibt das Tool sein eigenes Log) gesetzt werden. Das Working-Directory ist deshalb so wichtig da unser Service dort seine Einstellungen sucht. Dieses seltsame %~dp0 wird übrigens zum Verzeichnis erweitert in dem sich diese Bat-Datei befindet. Wirklich wichtig sind die Einstellungen StartClass, StopClass, StartParams und StopParams denn hier wird eingestellt das die Methode main (ist der default und kann auch noch eingestellt werden) in der Klasse de.wortzwei.simpleService.Service mit dem Parameter start zum starten bzw. stop zum stoppen aufgerufen wird. Die Main-Methode wird dabei nicht zum starten der VM aufgerufen sondern wird wie eine normale statische Methode aufgerufen.

Sehen wir uns die Main-Methode an:

public static void main(final String[] args) {
	// default ist "start"
	String cmd = CMD_START;
	if (args.length > 0) {
		cmd = args[0];
	}

	if (cmd.equalsIgnoreCase(CMD_START)) {
		Thread.setDefaultUncaughtExceptionHandler(new ExtUncaughtExceptionHandler());
		try {
			final Settings settings = new Settings();
			// den Server starten
			server = new Server(settings.getPort(),
			        settings.getMaxRequests(), new FileFactory(
			                settings.getOutput()));
			server.start();
		} catch (final IOException e) {
			System.out.println("fatal error!");
			e.printStackTrace();
			System.exit(-1);
		}
	} else {
		if (server != null) {
			server.shutdown();
			try {
				server.join();
			} catch (final InterruptedException e) {
				System.out.println("Interrupted?");
				e.printStackTrace();
			}
		}
	}
}

Wie man sieht wird der Server (ein Thread) gestartet wenn “start” als erster Parameter übergeben wird. Jeder andere String wird als “stop” interpretiert. Hier ist es wichtig beim stoppen nicht System.exit() aufzurufen. Das würde ja die JVM abbrechen, die wir nicht selber gestartet haben (da wir hier in der main Methode sind täuscht das ein wenig) und unter Windows werden dann wirre Meldungen angezeigt von wegen der Service konnte nicht gestoppt werden. Im Prinzip macht der Service-Wrapper nichts anderes als dieses kleine Programm:

/**
 * Diese Methode startet den Service; wartet ein bisschen und stoppt ihn dann
 * wieder. Ähnlich wie der Service-Wrapper das macht.
 */
private static void startStopService() {
	// starten
	Service.main(new String[] { "start" });

	// warten
	try {
		Thread.sleep(1000);
	} catch (final InterruptedException e) {
		System.out.println("Interrupted?");
		e.printStackTrace();
	}

	// stoppen
	Service.main(new String[] { "stop" });
}

Wie bereits erwähnt ist das beste an diesem Ansatz das ich diese Main-Methode fast ohne Änderung so unter Linux auch verwenden kann. Unter Linux ruft das init.d Script zum stoppen des Daemons nur kill -15 <pid> auf (SIGTERM). Wenn man jetzt einen Shutdown-Hook registriert kann dieser das stoppen des Server-Threads übernehmen und mit ein bisschen Refactoring kann der Code der jetzt im Else-Zweig steht sogar von beiden Ansätzen verwendet werden.

problem soved! bis denn…

Tags: , , , , , , ,
Kategorie: Java, Linux/Unix, Softwareentwicklung

Sie können die Kommentare für diesen Post mit diesem RSS 2.0 Feed verfolgen. Hinterlassen sie einen Kommentar oder einen Trackback von ihrem Blog.

Es gibt einen Kommentar zu diesem Post.

dirk [6. November 2011 09:32]

Das Beispiel nimmt übrigens jede Verbindung entgegen und schreibt die Bytes die es empfängt in eine Datei. Ihr könnt also dem Service mit

cat <file> | netcat localhost 33333

ein paar Bytes zuwerfen.

Hinterlasse einen Kommentar: