gentoo linux, java, software libre y otras hierbas
nov, 21 2009 - 9:16 pm

Ejecutar aplicación Java como servicio – Integración básica

En esta entrada abordaremos un tema bastante interesante: cómo correr un programa en Java como servicio en Windows o demonio en UNIX/Linux. Puesto que el API de Java no proporciona nada para estos casos, utiliza

remos una librería llamada Java Service Wrapper. Dicha librería nos ofrece una serie de scripts y binarios preparados para diferentes sistemas operativos y arquitecturas, que nos permitirán correr nuestros programas como un servicio; además ofrece diferentes versiones: Profesional, Estándar y Comunity. En este caso usaremos la versión Comunity que es libre y gratuita.

La mejor manera de aprender a usar dicha librería es con un pequeño laboratorio, así que he preparado uno en donde explico la integración más simple que existe con Java Service Wrapper (existen 4 métodos; unos más avanzados que otros). La integración sencilla que he escogido nos permitirá ejecutar como servicio una aplicación que ya esté creada (probablemente ya empaquetada en un .jar) y a la que no podemos o es difícil hacerle modificaciones. Esta es la manera más sencilla de hacerlo, sin embargo tiene una desventaja: al detener el servicio se envía directamente un System.exit() a la JVM por lo que la aplicación no se cerrará limpiamente.

Si quieres integrar tu aplicación de una manera más segura y estás en la capacidad de prepararla para ello, podrías intentar la integración avanzada en la que utilizas el API de Java Service Wrapper para implementar métodos de inicio, pausa y detención del servicio [Para este caso utilizaremos una aplicación de ejemplo que recibe un archivo como parámetro y escribe en él información sobre la memoria del sistema, cada 60 segundos. He escogido este ejemplo porque posee varios aspectos interesantes: requiere de librerías externas (tanto JARs como librerías nativas [.so, dll, etc.]) y recibe parámetros.

Puedes descargar la aplicación de ejemplo de haciendo clic aquí. Si la ejecutáramos de manera convencional veríamos algo de este tipo:

gentookde@larry bin % java -jar servicio.jar /tmp/log
^C
gentookde@larry bin % cat /tmp/log
======Mon Oct 05 09:42:05 COT 2009=======
Cantidad de memoria RAM: 3952MB
Total: 4042664
Usada: 3509516
Disponible: 533148
Memoria SWAP total: 1052248
Memoria SWAP usada: 0
Memoria SWAP libre: 1052248

Como puedes ver se trata de una aplicación de consola común y corriente. La aplicación consta básicamente de:

  • El ejecutable servicio.jar ubicado en el directorio bin/
  • La librería sigar.jar en el directorio lib/
  • Las librerías específicas de cada arquitectura de sigar en lib/

Pasos para la integración

Lo primero es descargar el paquete de Java Service Wrapper y descomprimirlo. Supondremos que el directorio donde se encuentra Java Service Wrapper es $RUTA_JSW y que el directorio de la aplicación que  vamos a convertir en servicio es $RUTA_APP.

1. Copiamos los siguentes archivos al directorio bin de nuestra aplicación de ejemplo…

UNIX/Linux

cp $RUTA_JSW/bin/wrapper $RUTA_APP/bin/
cp $RUTA_JSW/src/bin/sh.script.in $RUTA_APP/bin/
cp $RUTA_JSW/lib/* $RUTA_APP/bin/

El archivo $RUTA_JSW/bin/wrapper podría no existir; por lo tanto lo que debes copiar es el archivo wrapper-xxx específico para tu plataforma. Por ejemplo, en este caso estoy usando Gentoo Linux de 64bits por lo tanto copiaré el archivo $RUTA_JSW/bin/wrapper-linux-x86-64.

Windows

copy $RUTA_JSW/bin/ wrapper-windows-x86-32.exe $RUTA_APP/bin/
copy $RUTA_JSW/src/bin/ App.bat.in $RUTA_APP/bin/
copy $RUTA_JSW/src/bin/ InstallApp-NT.bat.in $RUTA_APP/bin/
copy $RUTA_JSW/src/bin/ UninstallApp-NT.bat.in $RUTA_APP/bin/
copy $RUTA_JSW/lib/* $RUTA_APP/bin/

2. Renombramos algunos archivos…

UNIX/Linux…

Ahora debes renombrar el script sh.script.in con el nombre de la aplicación con la que haremos la integración y darle privilegios de ejecución. En este casó se llama simplemente “servicio”:

cd $RUTA_APP/bin
mv sh.script.in servicio
chmod a+x servicio

Windows

Ahora debes renombrar los archivos .bat  para que concuerden con el nombre de la aplicación con la que haremos la integración. En este casó se llama simplemente “servicio” (lo puedes hacer usando el explorador si no te gusta usar la consola, pero a mi me daría pena):

cd $RUTA_APP/bin
move App.bat.in servicio.bat
move InstallApp-NT.bat.in InstallServicio-NT.bat
move UninstallApp-NT.bat.in UninstallServicio-NT.bat

3. editar archivo de configuración

Editamos el archivo wrapper.conf y lo dejamos en el directorio $RUTA_APP/conf/. El archivo para este ejemplo tendrá el siguiente contenido (importante! lee la explicación si usas Windows):

# Comando de Java
wrapper.java.command=java
# Clase que ejecutara el Wrapper
wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp
# Librerias necesarias para ejecutar el programa
wrapper.java.classpath.1=../lib/wrapper.jar
wrapper.java.classpath.2=servicio.jar
# Java Library Path (ubicacion de las librerias wrapper.dll o wrapper.so)
wrapper.java.library.path.1=../lib
# Java Bits.  On applicable platforms, tells the JVM to run in 32 or 64-bit mode.
wrapper.java.additional.auto_bits=TRUE
# Parametros del programa
wrapper.app.parameter.1=net.casidiablo.servicio.Servicio
wrapper.app.parameter.2=/tmp/log
#********************************************************************
# Wrapper General Properties
#********************************************************************
# Allow for the use of non-contiguous numbered properties
wrapper.ignore_sequence_gaps=TRUE
# Title to use when running as a console
wrapper.console.title=servicio
#********************************************************************
# Wrapper Windows NT/2000/XP Service Properties
#********************************************************************
# Name of the service
wrapper.name=jgossip
# Display name of the service
wrapper.displayname=Servicio
# Description of the service
wrapper.description=Monitor de memoria
# Mode in which the service is installed. AUTO_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START
# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=false

Explicación del archivo de configuración:

  • wrapper.java.command=java es el comando para ejecutar java. En este caso es simplemente java porque estoy en Linux y además tengo configurado correctamente el path. En Windows podría ser algo como: c:\archivos de programa\java\jdk1.6\bin\java.exe
  • wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp indica el archivo a ejecutar es la clase WrapperSimpleApp que se usa en la integración sencilla. Esta clase envoltorio es la que permite ejecutar la aplicación como servicio.
  • wrapper.java.classpath.1=../lib/wrapper.jar
    wrapper.java.classpath.2=servicio.jar
    indican las rutas o archivos del classpath.
  • wrapper.java.library.path.1=../lib indica el directorio donde se encuentran las librerías a usar.
  • wrapper.app.parameter.1=net.casidiablo.servicio.Servicio
    wrapper.app.parameter.2=/tmp/log
    estos son los parámetros que se pasarán al programa. Importante: como estamos haciendo la integración simple, el primer parámetro debe ser la clase que contiene el método main de nuestra aplicación.
  • wrapper.name=jgossip esta opción es para Windows y define el nombre del servicio en el sistema.
  • wrapper.ntservice.starttype=AUTO_START si estás en windows, define si el servicio quedará configurado para iniciar automáticamente.

4. Probar/Instalar el servicio…

Ahora veremos cómo podemos probar la aplicación para asegurarnos que ha quedado bien configurada antes de proceder con la instalación.

UNIX/Linux

Ejecutamos el script ‘servicio’ que configuramos en el paso 1 y 2 con el parámetro console:

./servicio console

Deberías ver algo como:

Running un servicio que escribe cosas…
wrapper  | –> Wrapper Started as Console
wrapper  | Java Service Wrapper Community Edition 64-bit 3.3.6
wrapper  |   Copyright (C) 1999-2009 Tanuki Software, Ltd.  All Rights Reserved.
wrapper  |     http://wrapper.tanukisoftware.org
wrapper  |
wrapper  | Launching a JVM…
jvm 1    | WrapperManager: Initializing…

Lo detienes presionando Ctrl+C. Ahora, si queremos probarlo como demonio basta con ejecutar los comandos típicos de un demonio en UNIX/Linux:

$ ./servicio start
Starting un servicio que escribe cosas…
$ ./servicio stop
Stopping un servicio que escribe cosas…
Stopped un servicio que escribe cosas.

Para instalarlo basta con crear un enlace simbólico del script en el directorio /etc/init.d y añadirlo al listado de servicios a iniciar; por ejemplo:

sudo ln -sv $RUTA_APP/bin/servicio /etc/init.d/servicio
sudo rc-update add servicio default

Windows

Para instalarlo en Windows basta con ejecutar el archivo InstallServicio-NT.bat. Puedes verificar que fue instalado correctamente accediendo a la consola de administración de servicios de NT (ejecutas el comando services.msc):

servicio windows java

Fuentes y descargas

Aunque en esta entrada no explico cómo hacer la integración avanzada, he preparado un ejemplo completo que puedes descargar en el listado de abajo.




34 Comentarios | deja el tuyo

34 Comentarios en “Ejecutar aplicación Java como servicio – Integración básica”

  1. Felipe dice:

    Ayuda a mi me sale este error…

    Unable to locate any of the following binaries:
    /opt/infra/aplic/wrapper/bin/wrapper-linux-x86-32 (Found but not executable.)
    /opt/infra/aplic/wrapper/bin/wrapper-linux-x86-64 (Found but not executable.)
    /opt/infra/aplic/wrapper/bin/wrapper (Found but not executable.)

  2. Cristian dice:

    Hola Felipe.

    Dos preguntas… ¿esos archivos realmente existen? ¿tienen permiso de ejecución?

    En caso que existan, puedes asegurarte que tengan permisos de ejecución… por ejemplo puedes ejecutar:

    chmod a+x /opt/infra/aplic/wrapper/bin/wrapper-linux-x86-32

    Un Saludo!

  3. Felipe dice:

    Me respondo, faltaba los permisos a
    wrapper-linux-x86-64, pero ahora me sale este error ….. :

    WARNING: [nombre running] may have failed to start.

  4. Cristian dice:

    Umm… eso no dice mucho :D Si pudieras explicar mejor cuando y como te sale eso… el comando que estabas ejecutando, un pantallazo o algo.

    un saludo!

  5. Felipe dice:

    Me ha resultado con una aplicación que desarrolle la cual manda un correo.
    El problema es: cuando escribo ./servicio console y termina de ejecutarse el programa inmediatamente se para el servicio , o sea no queda activo. Hay alguna variable en el wrapper.conf que hace que se detenga el servicio, como hago para dejarlo siempre activo ¿¿??

  6. Cristian dice:

    Hola Felipe…

    Eso debe suceder puesto que tu programa es de los que se cierra :D Es decir, supongo que en algún lado tendrás un System.exit(); Eso por supuesto hará que el servicio se detenga.

    Ahora… ¿cómo hacer para que no se detenga? Bien, la manera más sencilla de hacerlo (y ojo, que eso lo debes hacer en tu programa NO en el wrapper) es como en este ejemplo… si te bajas el código fuente te darás cuenta que cuando se ejecutan el método main uno de los métodos que ejecuta se queda en un ciclo infinito, esto se logra con expresiones como:

    while(true){
    //lo que quieras
    }

    Pero ten cuidado… debes implementar algo para que no sobrecargues el procesador… en el ejemplo se usan hilos y lo que haces es poner a dormir el hilo cada cierto tiempo. De lo contrario vas a tener un ciclo infinito que hará millones de cosas por minuto lo cual va a poner al procesador al tope.

    Espero te sirva lo que te escribí… un saludo!

  7. Felipe dice:

    Ajajaj muchas gracias Cristian…
    Después de ver tu código me di cuenta del while y el Thread….
    Yo juraba que el Wrapper era como una especie de crontab pero con función de servicio que se ejecutaba por ciclos, ahora me quedo todo mucho más claro.
    Gracias Cristian…

  8. Felipe dice:

    Excelente, he desarrollado una aplicación que verifica el tamaño del disco duro y cuando su porcentaje sea mayor a 80% envía un correo, y claro gracias a JSW he logrado hacerlo servicio el cual se ejecuta cada dos horas.

    Gracias por los manuales y ejemplos…

  9. Cristian dice:

    @Felipe… de hecho esto que puse lo aprendí haciendo algo muy similar…

    Puedes ver el código de ese programa que hice aquí:

    http://github.com/casidiablo/jgossip

    Un saludo!

  10. fermin dice:

    Hola

    Me fue dificil seguir las instrucciones

    Saludos

  11. pedro hernadez dice:

    hola tengo un problema pude cargar la aplicacion como servicio en windows xp pero al momento de iniciarla me tira este error “l servicio ClienteJava terminó con el error específico de servicio 1 (0×1).
    ” les cuento un poco de que se trata la aplicacion que necesito montar, la idea es tener un socket corriendo en todo momento para que se comunique con un mini servidor, esto lo estoy ocupando para poder logearme remotamente remotamente en windows(pero eso es otra historia) el tema es que no se que significa el error que me aparecio si alguien me podria ayudar le lo agradeceria es muy importante que me este funcionando……..

    PD: si alguien sabe alguna forma mas facil da hacer una aplicacion para login remoto tambien me puede ayudar(pedroinformatico@gmail.com)…..

    de antemanos gracias…..

  12. Cristian dice:

    Hola Pedro.

    En realidad tienes dos opciones:

    * O solucionas el problema que tienes con el servicio.
    * O haces que tu programa corra oculto en el systray de Windows. El problema de este método es que siempre tendrías que tener una sesión abierta para que se esté ejecutando, no como con el servicio que siempre estaría corriendo.

    Respecto al error… ¿te aparece al probarlo con el administrador de servicios de Windows? ¿o haciendo una prueba manual del servicio con el wrapper?

    Un saludo!

  13. pedro hernadez dice:

    me respondi solo al problema de mi error era que no tenia la carpeta lib donde correspondia pero la otra pregunta sigue en pie por todo gracias.

  14. pedro hernadez dice:

    gracias cristien …………….

  15. Juan Carlos dice:

    hola Pedro estoy siguiendo las instrucciones del laboratorio y puedo instalar el servicio pero al darle iniciar sale este error:
    No se puede iniciar el servicio Wrapper en Equipo local.
    Error 1067: El proceso ha terminado de forma inesperada.

    en el log veo que al parecer no encuentra el metodo main de la clase o algo asi sale esto:

    STATUS | wrapper | 2010/03/23 19:13:45 | –> Wrapper Started as Service
    STATUS | wrapper | 2010/03/23 19:13:45 | Launching a JVM…
    INFO | jvm 1 | 2010/03/23 19:13:45 | java.lang.NoClassDefFoundError: Main
    INFO | jvm 1 | 2010/03/23 19:13:45 | Caused by: java.lang.ClassNotFoundException: Main
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.net.URLClassLoader$1.run(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.security.AccessController.doPrivileged(Native Method)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.net.URLClassLoader.findClass(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.lang.ClassLoader.loadClass(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.lang.ClassLoader.loadClass(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | at java.lang.ClassLoader.loadClassInternal(Unknown Source)
    INFO | jvm 1 | 2010/03/23 19:13:45 | Could not find the main class: Main. Program will exit.
    INFO | jvm 1 | 2010/03/23 19:13:45 | Exception in thread “main”
    ERROR | wrapper | 2010/03/23 19:13:45 | JVM exited while loading the application.

    cual podria ser el problema? teniendo en cuenta que estoy usando la fuente del ejemplo, gracias

¡Déjanos tu comentario!