サービス・アプリケーション

サービス ・ アプリケーションはユーザーインターフェースを持たない常駐型のアプリケーションです。ユーザーがログオンしていなくてもコンピューターの起動時に自動的にプロセスを開始することができます。Windows サービスや NT サービスと呼ばれることもあります。

Web サーバーはサービス ・ アプリケーションの代表例の 1 つです。

オプション -s を指定するとサービス ・ アプリケーションになります。

サービス・アプリケーションの特徴

サービス ・ アプリケーションでは main メソッドは必要ありません。代わりにメインクラスには以下のシグネチャを持つ start メソッドと stop メソッドを定義してください。

public static void start(String[] args)
public static void stop()

これらのメソッドは、 Windows のサービス ・ コントロール ・ マネージャー (SCM) によって呼び出されます。start メソッドはサービスを開始したときに、 stop メソッドはサービスを停止したときに呼び出されます。

一般的に start メソッドはサービス機能を提供している間は終了しないようにループで実装します。stop メソッドが呼ばれたときに start メソッドのループを抜けて start メソッドが終了するようにしてください。

stop メソッドが呼び出されても start メソッドが終了しない実装になっていると、 Windows のサービス ・ コントロール ・ マネージャー (SCM) からサービスの停止操作をおこなっても反応しません。

簡単なプログラムを作ってみよう

固定メッセージを返す単純な Web サーバーを作って EXE にしてみましょう。

以下のソースコードを ServiceSample1.java というファイル名で作成します。

ServiceSample1.java
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; public class ServiceSample1 { private static AtomicBoolean isStopRequested = new AtomicBoolean(false); private static ServerSocket serverSocket; public static void start(String[] args) throws IOException { serverSocket = new ServerSocket(80, 50, InetAddress.getLoopbackAddress()); for(;;) { Socket socket; try { socket = serverSocket.accept(); } catch(SocketException e) { if(isStopRequested.get()) { return; } throw e; } new Worker(socket).start(); } } public static void stop() throws IOException { isStopRequested.set(true); if(serverSocket != null) { serverSocket.close(); } } private static class Worker extends Thread { private Socket socket; private BufferedReader reader; private BufferedWriter writer; public Worker(Socket socket) throws IOException { this.socket = socket; this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)); } @Override public void run() { try { for(;;) { String line = reader.readLine(); if(line == null || line.length() == 0) { break; } } writer.write("HTTP/1.1 200 OK¥r¥n"); writer.write("Content-Type: text/html; charset=utf-8¥r¥n"); writer.write("¥r¥n"); writer.write("Hello, World!! ("); writer.write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); writer.write(")¥r¥n"); writer.close(); } catch(Exception e) { throw new RuntimeException(e); } } } }

コマンドプロンプトを起動します。Java コンパイラ (javac) と exewrap が使えるように PATH 環境変数を設定しておいてください。

javac コマンドでソースコード ServiceSample1.java をコンパイルすると ServiceSample1.class と ServiceSample1$Worker.class ファイルが出来上がります。

コマンドプロンプト
C:¥>javac ServiceSample1.java

出力された ServiceSample1.class と ServiceSample1$Worker.class ファイルを Java アーカイブ形式 (jar) にまとめます。(対象となるクラスファイルが 2 つあるのでワイルドカードを使って *.class と指定しています。)

コマンドプロンプト
C:¥>jar cf ServiceSample1.jar *.class

最後に exewrap で ServiceSample1.jar を実行ファイル (EXE) にします。

このとき、 サービス ・ アプリケーションにするために -s オプションを指定します。-M オプションで start メソッドと stop メソッドを定義したメインクラスも指定します。(-M オプションを使わずにメインクラスを指定したマニフェストファイルを jar ファイルに含めておいても構いません。)

コマンドプロンプト
C:¥>exewrap -s -M ServiceSample1 ServiceSample1.jar exewrap 1.6.0 for x64 (64-bit) Architecture: x64 (64-bit) Target: Java 5.0 (1.5.0.0) SwingSample1.exe (64-bit) version 0.0.0.0

実行ファイルが出来上がりました。

実行ファイルには製品名や著作権表示、 バージョンなど様々なプロパティ情報を指定することができます。詳細については 「基本オプション」 を参照してください。

コンソールから実行してみる

サービス ・ アプリケーションは通常プロセスとしてコンソールから実行することもできます。まずはインストールせずにコンソールから実行してみましょう。

コマンドプロンプト
C:¥>ServiceSample1.exe

プロンプトに復帰せずにキャレットが点滅した状態になれば成功です。プロセスが起動して要求を受け付けられる状態になっています。

この状態でブラウザーを起動して http://localhost/ にアクセスしてみます。

Hello, World!!のメッセージと現在時刻が表示されれば OK です。上手く行きましたね!

サービス ・ アプリケーションをコンソールから起動した場合は Ctrl + C でプロセスを停止できます。

コマンドプロンプト
C:¥>ServiceSample1.exe CTRL_C: サービスを停止中です... C:¥>

start メソッドを実行していたスレッド以外に非デーモンスレッドが存在する場合、 すべての非デーモンスレッドが終了するまでプロセスは終了しません。

さらに Ctrl + C を押すと非デーモンスレッドが残っていても強制的にプロセスを終了します。

コマンドプロンプト
C:¥>ServiceSample1.exe CTRL_C: サービスを停止中です... CTRL_C: 強制終了します... C:¥>

サービス ・ アプリケーションをコンソールから起動すると、 標準出力や標準エラー出力の内容や例外発生時のスタックトレースを確認しやすいので開発 ・ デバッグ時にはコンソールからの起動が有効です。

サービスのインストール

作成したサービス ・ アプリケーションに、 引数 -install を指定して実行するとサービスとして登録されます。ServiceSample1.exe をサービスとして登録する場合は以下のようにします。(コマンドプロンプトを管理者権限で実行していない場合は UAC 権限昇格の確認画面が表示されます。)

コマンドプロンプト
C:¥>ServiceSample1.exe -install ServiceSample1 サービスをインストールしました。

サービス実行時の引数を指定する場合は、 -install の後に続けて記述してください。サービス実行時の引数は、 start メソッドの引数 String[] args として渡されます。

コマンドプロンプト
C:¥>ServiceSample1.exe -install foo bar

サービスのインストール時に、 サービスの構成を指定することもできます。サービス構成オプションは -install よりも前に記述します。たとえば、 サービスの実行アカウントを指定する場合は次のように指定します。詳細は 「サービス構成オプション」 を確認してください。

コマンドプロンプト
C:¥>ServiceSample1.exe -u Administrator -p mypassword -install

サービスの開始

コントロール パネル → 管理ツール → サービス から対象のサービスを選択して 開始 します。

サービスが正しく開始されると 「実行中」 と表示されます。

net start コマンドでを使用してサービスを開始することもできます。net start コマンドはサービス開始要求を送信した後、 サービスが開始されるまで一定時間待機してくれます。(同期)

コマンドプロンプト
C:¥>net start ServiceSample1 ServiceSample1 サービスを開始します. ServiceSample1 サービスは正常に開始されました。 C:¥>

sc start コマンドを使用してサービスを開始することもできます。sc start コマンドはサービス開始要求を送信した後、 すぐにプロンプトに復帰します。(非同期)

コマンドプロンプト
C:¥>sc start ServiceSample1 SERVICE_NAME: ServiceSample1 TYPE : 10 WIN32_OWN_PROCESS STATE : 2 START_PENDING (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 PID : 96 FLAGS : C:¥>

サービス開始要求を送信した直後は状態が START_PENDING となっておりサービスが正しく開始できたかどうか分かりません。

少し待ってから sc query コマンドで状態を確認してみてください。状態が RUNNING になっていればサービスは動作しています。

コマンドプロンプト
C:¥>sc query ServiceSample1 SERVICE_NAME: ServiceSample1 TYPE : 10 WIN32_OWN_PROCESS STATE : 4 RUNNING (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 C:¥>

いずれかの方法でサービスを開始してからブラウザーで http://localhost/ にアクセスしてみます。

サービスとしての動作も問題なしですね!

サービスの停止

コントロール パネル → 管理ツール → サービス から対象のサービスを選択して 停止 します。

サービスが正しく開始されると 「停止」 と表示されます。

net stop コマンドを使用してサービスを停止することもできます。net stop コマンドはサービス停止要求を送信した後、 サービスが停止するまで一定時間待機してくれます。(同期)

コマンドプロンプト
C:¥>net stop ServiceSample1 ServiceSample1 サービスを停止中です. ServiceSample1 サービスは正常に停止されました。 C:¥>

sc start コマンドを使用してサービスを停止することもできます。sc start コマンドはサービス停止要求を送信した後、 すぐにプロンプトに復帰します。

コマンドプロンプト
C:¥>sc stop ServiceSample1 SERVICE_NAME: ServiceSample1 TYPE : 10 WIN32_OWN_PROCESS STATE : 3 STOP_PENDING (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN) WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x7d0 C:¥>

サービス停止要求を送信した直後は状態が STOP_PENDING となっておりサービスが正しく停止できたかどうか分かりません。

少し待ってから sc query コマンドで状態を確認してみてください。状態が STOPPED になっていればサービスは停止しています。

コマンドプロンプト
C:¥>sc query ServiceSample1 SERVICE_NAME: ServiceSample1 TYPE : 10 WIN32_OWN_PROCESS STATE : 1 STOPPED WIN32_EXIT_CODE : 0 (0x0) SERVICE_EXIT_CODE : 0 (0x0) CHECKPOINT : 0x0 WAIT_HINT : 0x0 C:¥>

サービスのアンインストール

サービスのアンインストールは、 サービスが停止している状態でおこなってください。また、 イベントビューアが開いている状態では、 正しくサービスをアンインストールすることができないので注意してください。

コマンドプロンプト
C:¥>ServiceSample1.exe -remove ServiceSample1 サービスを削除しました。

サービスをアンインストール前にサービスの停止を要求することもできます。詳細は 「サービス構成オプション」 を確認してください。

exewrap の配布物に含まれている samples フォルダーには他にもサンプルコードがあります。ぜひ試してみてください。