Apache Daemon Tools e Quartz: scrivere un semplice servizio per Windows


    A seguire un semplice esempio che realizza un servizio di Windows partendo una applicazione Java, utilizzando nel dettaglio gli Apache Daemon Tools per la realizzazione del servizio, e la libreria Quartz per la gestione della schedulazione del servizio stesso.

    Per farlo ho preso spunto da questo articolo:

    http://ulrichpalha.com/myblog/2011/11/19/using-commons-daemon-procrun-to-run-your-java-application-as-a-windows-service/

    da cui ho preso parte del semplicissimo codice, e il file .bat per l’installazione e la rimozione del servizio.

    Testato con Windows 2003 Server, Windows 2008 Server e Windows 7.

    1) Apache Daemon Tools

    Apache Daemon Tools è un progetto della fondazione Apache, nato dalle costole di Tomcat 4, scritto in parte in C ed in parte in Java, che permette di trasformare una applicazione Java in un servizio, per Windows, o in un demone, per Unix.

    Come prima cosa scaricare l’ultima release (nel momento in cui scrivo è la 1.0.14) da qui:

    Questa è la directory ottenuta scompattando il file scaricato:

    d----        22/03/2013     12.48            amd64
    d----        22/03/2013     12.48            ia64
    -a---        15/03/2013      7.41      11358 LICENSE.txt
    -a---        15/03/2013      7.41        175 NOTICE.txt
    -a---        15/03/2013      7.44     104960 prunmgr.exe
    -a---        15/03/2013      7.44      80384 prunsrv.exe
    -a---        15/03/2013      7.41       5685 RELEASE-NOTES.txt

    Il file prunsrv.exe è il modulo che incapsula la nostra applicazione java in un eseguibile windows, che sarà poi installato come servizio. A seconda della versione del jre utilizzata (32 o 64 bit) va usato quello nella directory principale (32 bit) oppure quello nella cartella amd64 (64 bit).

    Per chi ha confidenza con l’installazione di Tomcat su Windows, quando quest’ultimo viene installato come servizio, vengono creati nella cartella bin questi due file eseguibili:

    -a---        10/01/2013     23.54      99840 Tomcat7.exe
    -a---        10/01/2013     23.54     103424 Tomcat7w.exe

    sono esattamente gli estessi eseguibile che ci ritroviamo sopra:

    Tomcat7.exe -> prunsrv.exe
    Tomcat7w.exe -> prunmgr.exe

    Per questo semplice esempio rinominiamo i file in questo modo:

    prunsrv.exe -> NicolaService.exe
    prunmgr.exe -> NicolaServiceManager.exe

    2) Le librerie Quartz

    Le librerie Quartz permettono la schedulazione di servizi (Job). Per fare questo esempio ho creato uno Scheduler, un Trigger ed un Job e ho schedulato il Trigger:

    import static org.quartz.CronScheduleBuilder.cronSchedule;
    import static org.quartz.JobBuilder.newJob;
    import static org.quartz.TriggerBuilder.newTrigger;
     
    import org.apache.log4j.Logger;
    import org.quartz.CronExpression;
    import org.quartz.JobDetail;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.impl.StdSchedulerFactory;
     
    //...
    //...
     
    // Il Job viene eseguito ogni minuto
    String cron = "0 0/1 * * * ?";
     
    // Se l'espressione di CRON non è valida segnalo ed esco. Il servizio termina.
    if (!CronExpression.isValidExpression(cron)) {
    	logger.debug("Espressione CRON non valida. Il servizio è terminato.");
    	return;				
    }
     
    sf = new StdSchedulerFactory();
    sche = sf.getScheduler();
     
    JobDetail job = newJob(NicolaJob.class)
    		.withIdentity("job1", "group1")
    		.build();
     
    Trigger trigger = newTrigger()
    		.withIdentity("trigger1", "group1")
    		.withSchedule(cronSchedule(cron))
    		.build();     
     
    sche.scheduleJob(job, trigger);
     
    logger.debug("Scheduler in fase di avvio...");
    sche.start();
    logger.debug("Scheduler avviato");

    dove NicolaJob.java è semplicemente:

    import org.apache.log4j.Logger;
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
     
    public class NicolaJob implements Job {
     
    	static private Logger logger = Logger.getLogger(NicolaJob.class.getName());
     
    	/**
    	 * Questo è il metodo che viene eseguito ad ogni schedulazione del Job
    	 */
    	public void execute(JobExecutionContext context)
    			throws JobExecutionException {
     
    		logger.debug("Inizio a fare quello che devo fare");
    		// ...
    		logger.debug("Ho finito di fare quello che devo fare");
    	}
     
    }

    3) Implementare il servizio in Java:

    Scrivere una classe che possa essere trasformata in servizio dai daemon tools è molto semplice. Questo è lo scheletro:

    public class NicolaService {
     
    	/**
    	 * Start the jvm version of the service, and waits for it to complete.
    	 * 
    	 * @param args
    	 *            optional, arg[0] = timeout (seconds)
    	 */
    	public static void start(String[] args) {
     
    		logger.debug("Metodo Start del servizio chiamato.");
    		//...
    		while (true) {
    			// Faccio qualcosa
    		}
    	}
     
    	/**
    	 * Stop the JVM version of the service.
    	 * 
    	 * @param args
    	 *            ignored
    	 */
    	public static void stop(String[] args) {
     
    		logger.debug("Metodo Stop del servizio chiamato.");
    		//...
     
    	}
    }

    4) Installazione e rimozione del servizio

    Utilizzando maven creo il jar con le dipendenze con il comando:

    mvn clean assembly:assembly

    La stessa cosa può essere fatta per esempio con Eclipse con la sequenza:

    File->Export->Java->Runnable Jar

    Una volta creato il jar creo una directory così fatta:

    d----        02/04/2013     17.41            bin_32
    d----        02/04/2013     17.42            bin_x64
    d----        02/04/2013     17.46            logs
    -a---        02/04/2013     17.36    1694511 daemontools-quartz-0.0.1-full.jar

    se ho un vm java a 32 bit utilizzo la directory bin_32, se ho una vm a 64 bit utilizzo la bin_x64.

    All’interno delle directories bin è presente, oltre i due file eseguili descritti sopra, anche un file .bat, che installa e disinstalla il servizio. Io l’ho modificato per andare a segnare quale classe java contiene i metodi per lo start e lo stop:

    ::-- 2. The fully qualified start and stop classes
    set CG_START_CLASS=nicola.test.NicolaService
    set CG_STOP_CLASS=%CG_START_CLASS%

    e per segnalare il percorso del jar:

    ::-- 5. the classpath for all jars needed to run your service
    set CG_PATH_TO_JAR_CONTAINING_SERVICE=%APPLICATION_SERVICE_HOME%\daemontools-quartz-0.0.1-full.jar

    Per installare il servizio:

    Install-Remove-Service.bat install

    Per disintallarlo:

    Install-Remove-Service.bat remove

    L’applicazione di controllo, nel mio caso NicolaServicew.exe, permette, una volta installato, di attivarlo, di modificare eventualmente i parametri del runtime di java e altro.

    Il servizio può essere gestito ovviamente anche dal pannello di controllo di Windows. Una volta che il servizio è avviato, può essere gestito come applicazione, e lanciata da riga di comando:

    C:\Sviluppo\Nicola\servizio\bin_32>NicolaService.exe
    2013-04-03 16:24:07,541 DEBUG nicola.test.NicolaService - Metodo Start del servizio chiamato.
    [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
    [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
    [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
    [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.1.7 created.
    [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
    [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.1.7) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
      Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
      NOT STARTED.
      Currently in standby mode.
      Number of jobs executed: 0
      Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
      Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
     
    [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.pr
    operties'
    [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.1.7
    2013-04-03 16:24:07,621 DEBUG nicola.test.NicolaService - Scheduler in fase di avvio...
    [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
    2013-04-03 16:24:07,622 DEBUG nicola.test.NicolaService - Scheduler avviato
    2013-04-03 16:25:00,009 DEBUG nicola.test.NicolaJob - Inizio a fare quello che devo fare
    2013-04-03 16:25:00,009 DEBUG nicola.test.NicolaJob - Ho finito di fare quello che devo fare
    2013-04-03 16:26:00,001 DEBUG nicola.test.NicolaJob - Inizio a fare quello che devo fare
    2013-04-03 16:26:04,537 DEBUG nicola.test.NicolaJob - Ho finito di fare quello che devo fare
    2013-04-03 16:26:23,470 DEBUG nicola.test.NicolaService - Metodo Stop del servizio chiamato.
    2013-04-03 16:26:23,471 DEBUG nicola.test.NicolaService - Scheduler in fase di arresto...
    [Thread-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
    [Thread-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
    [Thread-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
    2013-04-03 16:26:23,590 DEBUG nicola.test.NicolaService - Scheduler arrestato.

    Ho impacchettato tutto in un progetto di prova da scaricare.

      Leave a Reply

      Your email address will not be published.

      You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>