【Java】JavaをWindowsサービス化する ~ Apache Commons Daemon ~

■ はじめに

 JavaWindowsサービス化する方法を調べてみたら、
以下「JavaWindowsサービス化するには」のような方法があった
で、今回、「【1】 Apache Commons Daemon を使う」を扱う

補足:.NETでWindowsサービス化するには

 * 以下の関連記事を参照。
Windowsサービス ~ Hello World編 ~
https://blogs.yahoo.co.jp/dk521123/37948659.html

JavaWindowsサービス化するには

 【1】 Apache Commons Daemon を使う << 今回は、これを扱う
 【2】 Java Service Wrapper を使う
 【3】 winsw を使う
etc...

【2】 Java Service Wrapper を使う

http://www.tanukisoftware.com/ja/wrapper.php

【3】 winsw を使う

 * 作者は Jenkins の川口さん
 * Java だけでなく使える模様
  => 本当は、こっちを試したい。時間をある時にそのうち。。。
https://kiririmode.hatenablog.jp/entry/20170407/1491490800

Apache Commons Daemon を使ったサービス作成方法

[1] 以下のサイトから、必要なモジュールをダウンロードする
commons-daemon
http://commons.apache.org/proper/commons-daemon/download_daemon.cgi
http://ftp.jaist.ac.jp/pub/apache//commons/daemon/binaries/windows/
commons-logging
https://commons.apache.org/proper/commons-logging/download_logging.cgi
~~~~~~
 + commons-daemon-1.1.0-bin.zip
 + commons-daemon-1.1.0-bin-windows.zip
 + commons-logging-1.2-bin.zip
~~~~~~

[2] Windowsサービス用の処理をコーディングし、JARファイルを出力する
 => 今回は、JARファイルはEclipseの機能で、プロジェクト名を右クリックし、
    [Export]-[Java]-[JAR file]でJARファイルを作成した

[3] Windowsサービス用のインスール/アンインストール用のバッチファイルを作成する

[4] 64ビットOSの場合「commons-daemon-1.1.0-bin-windows/amd64/prunsrv.exe」を
   「【サービス名】.exe」にリネームする
 => 32ビットOSの場合は、prunsvr.exeを【サービス名】.exeにprunmgr.exeを
    【サービス名】w.exeにそれぞれリネーム

[5] ファイルを配置する(以下の「■ サンプル」の「ファルダ構成」を参照)

[6] Windowsサービス用のインスール用のバッチファイルを起動する

[7] Windowsサービスを開始する

■ サンプル

 * Windowsの場合、開始・終了用のstaticメソッドが必要 

Windowsサービス用Javaソース

HelloWorldServiceLauncher.java
package com.sample.service;

import java.util.Arrays;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HelloWorldServiceLauncher {
  private static Log LOG = LogFactory.getLog(HelloWorldServiceLauncher.class);
  private static IService service = null;
  private static HelloWorldServiceLauncher instance = new HelloWorldServiceLauncher();
  private ExecutorService executor = null;
  private static Scanner scanner;

  public static void main(String[] args) {
    if (args != null) {
      LOG.debug("Param : " + Arrays.toString(args));
    }

    HelloWorldServiceLauncher.start(null);

    scanner = new Scanner(System.in);
    LOG.debug("Enter 'stop' to halt: ");
    while (!scanner.nextLine().toLowerCase().equals("stop")) {
      ;
    }

    HelloWorldServiceLauncher.stop(null);
  }

  public static void start(String[] args) {
    if (args != null) {
      LOG.debug("Param : " + Arrays.toString(args));
    }
    instance.initialize();
  }

  public static void stop(String[] args) {
    if (args != null) {
      LOG.debug("Param : " + Arrays.toString(args));
    }
    instance.terminate();
  }

  public void initialize() {
    if (HelloWorldServiceLauncher.service == null) {
      HelloWorldServiceLauncher.service = new HelloWorldService();
    }

    this.executor = Executors.newSingleThreadExecutor();
    this.executor.execute(HelloWorldServiceLauncher.service);
  }

  public void terminate() {
    if (HelloWorldServiceLauncher.service != null) {
      HelloWorldServiceLauncher.service.stop();
    }
    if (this.executor != null) {
      this.executor.shutdown();
    }
  }
}
IService.java
package com.sample.service;

public interface IService extends Runnable {
  public void stop();
  public Boolean isStopped();
}
HelloWorldService.java
package com.sample.service;

import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HelloWorldService implements IService {
  private static Log LOG = LogFactory.getLog(HelloWorldService.class);
  private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
  private Boolean isStopped = Boolean.FALSE;

  @Override
  public void run() {
    while (!this.isStopped()) {
      LOG.debug("[ENTER]" + dateFormat.format(new Date()));
      try {
        // ★ここに処理を書く(今回はダミー処理を書いておく)★
        try (FileWriter fileWriter = new FileWriter("C:\\temp\\hello-world.txt", true);) {
          fileWriter.write(dateFormat.format(new Date()) + " : Hello World!\n");
        } catch (IOException ex) {
          LOG.error("IO Error", ex);
        }
        Thread.sleep(5_000L);

      } catch (InterruptedException ex) {
        LOG.error("Error", ex);
        this.isStopped = Boolean.TRUE;
      }
      LOG.debug("[EXIT]" + dateFormat.format(new Date()));
    }
  }

  @Override
  public void stop() {
    this.isStopped = Boolean.TRUE;
  }

  @Override
  public Boolean isStopped() {
    return this.isStopped;
  }
}

Windowsサービス用のインスール/アンインストール用のバッチファイル

install.bat
set EXEC_DIR=%~dp0
echo %EXEC_DIR%
set CLASSPATH_DIR=%EXEC_DIR%lib
echo %CLASSPATH_DIR%
set CLASSPATH=%EXEC_DIR%HelloWorldServiceLauncher.jar;%CLASSPATH_DIR%commons-daemon-1.1.0.jar;%CLASSPATH_DIR%commons-logging-1.2.jar;%CLASSPATH_DIR%\*;
echo %CLASSPATH%
set JVM_PATH="C:\Program Files\Java\jdk1.8.0\jre\bin\server\jvm.dll"

set INSTALL_PATH=%EXEC_DIR%HelloWorldServiceLauncher.exe
echo %INSTALL_PATH%

HelloWorldServiceLauncher //IS//HelloWorldService --DisplayName="Hello World Service" --Description="Demo for Hello World Service" ^
        --Install %INSTALL_PATH% --Startup auto --Jvm %JVM_PATH% --StartMode jvm --StopMode=jvm ^
        --Classpath=%CLASSPATH% --StartClass com.sample.service.HelloWorldServiceLauncher --StartMethod start --StartParams Hello#World#Start ^
        --StopClass com.sample.service.HelloWorldServiceLauncher --StopMethod stop --StopParams Hello#World#Stop ^
        --LogPath=%EXEC_DIR%logs --LogLevel=DEBUG ^
        --StdOutput=auto --StdError=auto ^


pause

uninstall.bat
HelloWorldServiceLauncher //DS//HelloWorldService

pause

ファルダ構成

C:\temp\
 + hello-world-service
    + HelloWorldServiceLauncher.exe (prunsrv.exe をリネーム)
    + HelloWorldServiceLauncher.jar
    + install.bat
    + uninstall.bat
    + lib
       + commons-daemon-1.1.0.jar
       + commons-logging-1.2.jar