2012年4月4日水曜日

commons-daemon 第3回

Windowsでのサービス登録方法 公式ページを参考にしてもどうにも動かないので、ちょっと頑張りが必要でした。  公式ページ → Daemon: Procrun

jarの作成

前回作成したJavaをJarにします。
ここでは面倒なので、Eclipseのエクスポート機能を使います。

成果物:EngineLauncher.jar

場所

とりあず、C:\tmp\test-serviceを作ります。
で、libフォルダにcommons-daemon-1.0.10.jar、commons-logging-1.1.1.jar、log4j-1.2.16.jarを入れ
logsフォルダも作っておきます。
あと、commons-daemon-1.0.10-bin-windows.zipをダウンロードしてあるはずなので、
その中にあるprunsrv.exeもC:\tmp\test-serviceに入れておきます。
C:\tmp\test-service
│  EngineLauncher.jar
│  install.bat
│  prunsrv.exe
│  uninstall.bat
│  
├─lib
│      commons-daemon-1.0.10.jar
│      commons-logging-1.1.1.jar
│      log4j-1.2.16.jar
│      
└─logs
        commons-daemon.2012-04-04.log
        testservice-stderr.2012-04-04.log
        testservice-stdout.2012-04-04.log

サービスへの登録バッチ

--Jvmオプションでjvm.dllの場所を指定するのですが、autoでうまく動かず、直接指定しました。autoのはずだと思うのですが…

  1. set EXEC_DIR=%~dp0  
  2. echo %EXEC_DIR%  
  3. set CLASSPATH_DIR=%EXEC_DIR%lib  
  4. echo %CLASSPATH_DIR%  
  5. set CLASSPATH=%EXEC_DIR%EngineLauncher.jar;%CLASSPATH_DIR%\commons-daemon-1.0.10.jar;%CLASSPATH_DIR%\commons-logging-1.1.1.jar;%CLASSPATH_DIR%\log4j-1.2.16.jar;  
  6. echo %CLASSPATH%  
  7. set JVM_PATH="C:\Program Files\Java\jdk1.6.0_31\jre\bin\server\jvm.dll"  
  8.   
  9. prunsrv //IS//TestService --DisplayName="Test Service" ^  
  10.         --Startup=auto ^ --Jvm=%JVM_PATH% --StartMode=jvm --StopMode=jvm ^  
  11.         --Classpath=%CLASSPATH% ^  
  12.         --StartClass=jp.tanakanbb.blogspot.daemon.service.sample.EngineLauncher --StartParams=start ^  
  13.         --StartMethod=windowsService ^  
  14.         --StopClass=jp.tanakanbb.blogspot.daemon.service.sample.EngineLauncher --StopParams=stop ^  
  15.         --StopMethod=windowsService ^  
  16.         --LogPath=%EXEC_DIR%logs --LogLevel=DEBUG ^  
  17.         --StdOutput=auto --StdError=auto ^  
  18.           
  19.           
  20.   
  21. pause  

サービスへの削除バッチ


  1. prunsrv //DS//TestService  
  2.   
  3. pause  

手順

install.batを実行すると、サービスに登録され、管理ツールからサービスを開いて、"Test Service"を開始すればサービスが始まります。
停止すれば止まります。
アンインストールは、uninstall.batです。

--Startup=autoだけど、install.batを実行しても開始されません...

追加で

commons-logging.properties

  1. org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger  
log4j.xmlはこんなかんじ。/var/log/daemon/application.logにログ出力します。

  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
  3.   
  4. <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>  
  5.   
  6.   <!-- ********************** 標準出力への出力 ********************** -->  
  7.   <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">  
  8.     <param name="threshold" value="info"/>  
  9.   
  10.     <!-- レイアウトの指定 -->  
  11.     <layout class="org.apache.log4j.PatternLayout">  
  12.       <param name="ConversionPattern"  
  13.              value=""%d{yyyy/MM/dd HH:mm:ss.SSS}",%p,%c,%M,%L,%m%n" />  
  14.     </layout>  
  15.   </appender>  
  16.   
  17.   <!-- ********************** ファイルへの出力 ********************** -->  
  18.   <appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">  
  19.     <param name="threshold" value="debug"/>  
  20.   
  21.     <!-- ファイル情報 -->  
  22.     <param name="file" value="/var/log/daemon/application.log" />  
  23.   
  24.     <param name="append" value="false" />  
  25.     <param name="datePattern" value="'.'yyyy-MM-dd" />  
  26.   
  27.     <!-- レイアウトの指定 -->  
  28.     <layout class="org.apache.log4j.PatternLayout">  
  29.       <param name="ConversionPattern"  
  30.              value=""%d{yyyy/MM/dd HH:mm:ss.SSS}",%p,%c,%M,%L,%m%n" />  
  31.     </layout>  
  32.   </appender>  
  33.   
  34.   <!-- ********************** category定義 ************************* -->  
  35.   <category name="jp.tanakanbb.blogspot">  
  36.     <priority value="debug" />  
  37.     <appender-ref ref="FILE" />  
  38.   </category>  
  39.   
  40.   <!-- ********************** root定義 ***************************** -->  
  41.   <root>  
  42.     <priority value="debug" />  
  43.     <appender-ref ref="STDOUT" />  
  44.   </root>  
  45.   
  46. </log4j:configuration>  

commons-daemon 第2回

まずはjavaソースから。 以下のwikiを参考にしてます。

参考 Daemon - Commons Wiki

ただ、肝心な部分がよく分からなかったので、カスタマイズしてます。 まずは、Daemonインターフェースを実装したクラス。
EngineLauncher クラスが本体で、こいつがEngine(インターフェースを実装した)クラスを起動してメイン処理を実行します。
停止するときも、Engine(インターフェースを実装した)クラスを停止します。

EngineLauncher

Windowsのサービスとして動くときは、windowsServiceを使用します。
procrun.exeは、public staticメソッドしか呼ぶことができません。
そして、実行時には引数を渡すことができますので
開始時には、"start"、停止時には"stop"を渡すことで、それぞれstartWindowsServiceとstopWindowsServiceメソッドを呼び出します。

mainメソッドはおまけです。使用しません。

Linuxで使用するjsvcでは、Daemonインターフェースを実装していると、
最初にinitが呼ばれ、続いてstartメソッドが呼ばれます。
停止するときは、"-stop"オプションをつけてjsvcを実行することでstopメソッドが呼び出されます。

  1. package jp.tanakanbb.blogspot.daemon.service.sample;  
  2.   
  3. import java.util.Scanner;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6.   
  7. import jp.tanakanbb.blogspot.daemon.service.sample.impl.SampleEngineImpl;  
  8.   
  9. import org.apache.commons.daemon.Daemon;  
  10. import org.apache.commons.daemon.DaemonContext;  
  11. import org.apache.commons.logging.Log;  
  12. import org.apache.commons.logging.LogFactory;  
  13.   
  14. public class EngineLauncher implements Daemon {  
  15.   
  16.     /** 
  17.      * ログ 
  18.      */  
  19.     private static Log LOG = LogFactory.getLog(EngineLauncher.class);  
  20.   
  21.     /** 
  22.      * エンジン 
  23.      */  
  24.     private static Engine engine = null;  
  25.   
  26.     /** 
  27.      * エンジン起動 
  28.      */  
  29.     private static EngineLauncher engineLauncherInstance = new EngineLauncher();  
  30.   
  31.     /** 
  32.      * Executor 
  33.      */  
  34.     private ExecutorService executor = null;  
  35.   
  36.     /** 
  37.      * The Java entry point. 
  38.      * 
  39.      * @param args 
  40.      *            Command line arguments, all ignored. 
  41.      */  
  42.     public static void main(String[] args) {  
  43.         // the main routine is only here so I can also run the app from the  
  44.         // command line  
  45.         engineLauncherInstance.initialize();  
  46.   
  47.         engineLauncherInstance.startWindowsService();  
  48.   
  49.         Scanner sc = new Scanner(System.in);  
  50.         // wait until receive stop command from keyboard  
  51.         System.out.printf("Enter 'stop' to halt: ");  
  52.   
  53.         while (!sc.nextLine().toLowerCase().equals("stop")) {  
  54.             ;  
  55.         }  
  56.   
  57.         engineLauncherInstance.stopWindowsService();  
  58.   
  59.     }  
  60.   
  61.     /** 
  62.      * Windowsサービスを起動します。 
  63.  
  64.      * サービス登録するときのパラメータは次のようです。 
  65.      * 
  66.      * <pre> 
  67.      * --StartMode=jvm --StartClass=jp.tanakanbb.blogspot.daemon.service.sample.EngineLauncher --StartMethod=windowsService --StartParams=start --StopMode=jvm --StopClass=jp.tanakanbb.blogspot.daemon.service.sample.EngineLauncher --StopMethod=windowsService --StopParams=stop 
  68.      * </pre> 
  69.      * 
  70.      * Windowsサービス、つまりprocrunで起動する場合、実行するメソッドは"public static void"である必要があります。 
  71.  
  72.      * voidでなくてもいいかもしれませんが、戻り値を解釈しないので、voidがいいと思います。 
  73.      * 
  74.      * @param args 
  75.      *            Arguments from prunsrv command line 
  76.      **/  
  77.     public static void windowsService(String args[]) {  
  78.         String cmd = "start";  
  79.         if (args.length > 0) {  
  80.             cmd = args[0];  
  81.         }  
  82.   
  83.         if ("start".equals(cmd)) {  
  84.             engineLauncherInstance.startWindowsService();  
  85.         } else {  
  86.             engineLauncherInstance.stopWindowsService();  
  87.         }  
  88.     }  
  89.   
  90.     /** 
  91.      * Windowsサービスの場合のスタートメソッド。 
  92.  
  93.      * Engine#execute()を起動します。 
  94.      */  
  95.     public void startWindowsService() {  
  96.         if (LOG.isDebugEnabled()) {  
  97.             LOG.debug("startWindowsService called");  
  98.         }  
  99.   
  100.         initialize();  
  101.         // don't return until stopped  
  102.         executor.execute(engine);  
  103.     }  
  104.   
  105.     /** 
  106.      * Windowsサービスを停止します。 
  107.      */  
  108.     public void stopWindowsService() {  
  109.         if (LOG.isDebugEnabled()) {  
  110.             LOG.debug("stopWindowsService called");  
  111.         }  
  112.   
  113.         terminate();  
  114.   
  115.         executor.shutdown();  
  116.     }  
  117.   
  118.     /** 
  119.      * Implementing the Daemon interface is not required for Windows but is for 
  120.      * Linux 
  121.  
  122.      * jsvcでの起動時に最初に呼ばれます。 
  123.  
  124.      * {@link Daemon#init(DaemonContext)} 
  125.      */  
  126.     @Override  
  127.     public void init(DaemonContext arg0) throws Exception {  
  128.         LOG.debug("Daemon init");  
  129.     }  
  130.   
  131.     /** 
  132.      * jsvcでの起動時に、{@link Daemon#init(DaemonContext)}の次に呼ばれます。 
  133.      */  
  134.     @Override  
  135.     public void start() {  
  136.         if (LOG.isDebugEnabled()) {  
  137.             LOG.debug("Daemon start");  
  138.         }  
  139.         initialize();  
  140.     }  
  141.   
  142.     /** 
  143.      * jsvcでの-stopオプション時に呼ばれます。 
  144.      */  
  145.     @Override  
  146.     public void stop() {  
  147.         if (LOG.isDebugEnabled()) {  
  148.             LOG.debug("Daemon stop");  
  149.         }  
  150.   
  151.         terminate();  
  152.     }  
  153.   
  154.     /** 
  155.      * {@link Daemon#stop()}後に呼ばれます。 
  156.      */  
  157.     @Override  
  158.     public void destroy() {  
  159.         if (LOG.isDebugEnabled()) {  
  160.             LOG.debug("Daemon destroy");  
  161.         }  
  162.     }  
  163.   
  164.     /** 
  165.      * Engineを初期化します。 
  166.  
  167.      * WindowsでもLinuxでも、最初にこれを呼ぶ必要があります。 Do the work of starting the engine 
  168.      */  
  169.     private void initialize() {  
  170.         if (engine == null) {  
  171.             if (LOG.isInfoEnabled()) {  
  172.                 LOG.info("Starting the Engine");  
  173.             }  
  174.             engine = new SampleEngineImpl();  
  175.         }  
  176.   
  177.         executor = Executors.newSingleThreadExecutor();  
  178.     }  
  179.   
  180.     /** 
  181.      * Windowsサービス、デーモンを停止します。 WindowsでもLinuxでも、停止時には、これを呼ぶ必要があります。 Cleanly stop 
  182.      * the engine. 
  183.      */  
  184.     public void terminate() {  
  185.         if (engine != null) {  
  186.             if (LOG.isInfoEnabled()) {  
  187.                 LOG.info("Stopping the Engine");  
  188.             }  
  189.   
  190.             engine.stop();  
  191.   
  192.             if (LOG.isInfoEnabled()) {  
  193.                 LOG.info("Engine stopped");  
  194.             }  
  195.         }  
  196.     }  
  197.   
  198. }  

Engine


  1. package jp.tanakanbb.blogspot.daemon.service.sample;  
  2.   
  3. public interface Engine extends Runnable {  
  4.   
  5.     public void stop();  
  6.   
  7.     public Boolean isStopped();  
  8.   
  9. }  

SampleEngineImpl


  1. package jp.tanakanbb.blogspot.daemon.service.sample.impl;  
  2.   
  3. import jp.tanakanbb.blogspot.daemon.service.sample.Engine;  
  4.   
  5. import org.apache.commons.logging.Log;  
  6. import org.apache.commons.logging.LogFactory;  
  7.   
  8. public class SampleEngineImpl implements Engine {  
  9.   
  10.     /** 
  11.      * ログ 
  12.      */  
  13.     private static Log LOG = LogFactory.getLog(SampleEngineImpl.class);  
  14.   
  15.     /** 
  16.      * 停止フラグ 
  17.      */  
  18.     private Boolean isStopped = Boolean.FALSE;  
  19.   
  20.     /** 
  21.      * スレッドID 
  22.      */  
  23.     private String id = null;  
  24.   
  25.     @Override  
  26.     public void stop() {  
  27.         this.isStopped = Boolean.TRUE;  
  28.     }  
  29.   
  30.     @Override  
  31.     public Boolean isStopped() {  
  32.         return isStopped;  
  33.     }  
  34.   
  35.     @Override  
  36.     public void run() {  
  37.   
  38.         this.id = String.valueOf(Thread.currentThread().getId());  
  39.   
  40.         while (!isStopped()) {  
  41.             if (LOG.isInfoEnabled()) {  
  42.                 LOG.info("[" + id + "]" + "hoge");  
  43.             }  
  44.   
  45.             try {  
  46.                 Thread.sleep(10000);  
  47.             } catch (InterruptedException e) {  
  48.                 isStopped = Boolean.TRUE;  
  49.                 if (LOG.isErrorEnabled()) {  
  50.                     LOG.error(e);  
  51.                 }  
  52.             }  
  53.         }  
  54.     }  
  55.   
  56. }  
さて、第3回では、Windowsサービスへの登録方法とLinuxデーモン化方法を書きたいと思います。