Web Services en Axis2 con Spring y Gradle

Recientemente en un proyecto un cliente nos dijo que la interfaz entre nuestros sistemas sería... esperen... sí... un web service. Un web service que ellos invocarían y que por lo tanto nosotros tenemos que implementar. Así que nos dieron un par de archivos WSDL y unos ejemplos de XML para asegurarnos que salga como debe ser (no entiendo para qué, si todas las herramientas de generación de web services se encargan de que el XML siempre salga como debe ser, ¿verdad?).

Cuando tengo que invocar un web service, generalmente uso Axis2. Me ha resultado de lo más sencillo porque consiste simplemente en ejecutar el script wsdl2java dándole el URI del WSDL y con eso tengo una clase que puedo usar para invocar los servicios (junto con mil clases internas que envuelven los datos dentro de envoltorios intermedios porque parece que los de Axis entendieron OOP como Onion-Oriented Programming). Así que en esta ocasión decidí probar el otro lado de la implementación: publicar un servicio hecho con Axis2.

La primera parte es bastante sencilla y muy similar a la de hacer un cliente: Con el script wsdl2java se genera el código para el servicio, incluyendo un script de Ant para crear un archivo aar que contiene el servicio (detalles más adelante). Aquí en vez de un stub que sirva para invocar el servicio, nos queda una clase con los métodos del servicio, en donde tenemos que meterle código para que hagan algo. Y aquí es donde empieza lo interesante.

Antes de empezar a echar código me puse a investigar cómo iba a ser el deploy de esta cosa. Resulta que Axis2 ofrece un war que se puede aventar en cualquier contenedor de servlets y funciona como una aplicación para publicar web services; simplemente hay que empacar los servicios en formato AAR (supongo que de Axis ARchive) y echarlos dentro del WEB-INF/services del directorio "explotado" (o dentro del axis2.war antes de meterlo al contenedor). El formato del AAR incluye un archivo services.xml similar a un web.xml, con la definición de los servicios. El script wsdl2java me genera ese archivo pero pues lo puedo modificar después. Con eso y las clases ya tengo un servicio básico que puedo probar de inmediato.

Una implementación real

Pero una vez que tenemos este esqueleto, qué podemos hacer para implementar un servicio real? Digamos, algo que se conecta a base de datos, que utiliza varios componentes que incluso ya tenemos en otras bibliotecas de nuestros proyectos, que use Spring para conectar dichos componentes, etc?

Para la parte de incluir componentes existentes, simplemente agregamos jars a un directorio lib que debe quedar dentro del AAR y estarán disponibles para nuestro servicio. Y para Spring... ah, eso sí es un poco más complicado; resulta que Axis2 tiene soporte para Spring, para integrar de manera sencilla un applicationContext con el servicio que vamos a implementar, pero el soporte es para Spring 2.5, lo cual está muy bien para la gente que sigue programando en 2008, pero para los que estamos en 2013 usando Spring 3.x no sirve de nada. Me consta porque intenté usar este soporte y simplemente ya no funciona porque los componentes de Axis2 para Spring implementan interfaces que fueron eliminadas en Spring 3.

Así que tuve que hacer mi propio componente, después de buscar en los repositorios de Apache para encontrar las implementaciones de los componentes originales de Axis2 para integrar Spring. En mi caso no fue complicado, aunque algo engorroso; tuve que hacer una clase que guardara el application context en una variable estática y que lo cargue de manera sincronizada cuando lo necesita y no existe. Y de paso implementar una interfaz de Axis2 para proveer los objetos que implementan servicios. Ah y lo implementé en Groovy. Algo así:

class SpringLoader implements ServiceObjectSupplier {

  private static final Object lock = new Object()
  private static ClassPathXmlApplicationContext ctxt

  static ClassPathXmlApplicationContext getContext(AxisService service) {
    if (ctxt == null) {
      synchronized(lock) {
        if (ctxt == null) {
          //Crear con el constructor que NO carga los beans de inmediato
          ctxt = new ClassPathXmlApplicationContext(['/mi-contexto.xml'], false)
          //Cambiar el classLoader por el del servicio
          ctxt.classLoader = service.classLoader
          //Cargar los beans
          ctxt.refresh()
          //Registrar la rutina para shutdown
          ctxt.registerShutdownHook()
        }
      }
    }
  }

  Object getServiceObject(AxisService axisService) throws AxisFault {
    //El objeto axisService trae parametros, buscamos el nombre del bean
    Parameter implBeanParam = axisService.getParameter("SpringBeanName")
    //Ahora lo convertimos a String
    String beanName = ((String)implBeanParam.getValue()).trim()
    //Y buscamos un bean con ese nombre
    return getContext(axisService).getBean(beanName)
  }

}

Ahora, para que todo esto funcione, hay que ponerlo en la definición del servicio, por lo que hay que modificar el services.xml:

<serviceGroup>
  <service name="miServicio">
    <messageReceivers><!--esto no cambia--></messageReceivers>
    <parameter name="ServiceObjectSupplier">ezl.ejemplo.SpringLoader</parameter>
    <parameter name="SpringBeanName">miServicio</parameter>
    <!-- luego siguen las operaciones, eso no cambia -->
  </service>
</serviceGroup>

Por cierto, aquí en este ejemplo estoy suponiendo que el archivo de Spring se llama mi-contexto.xml y se encuentra dentro del directorio raíz del AAR. Y en ese contexto debe existir un bean llamado miServicio, de la clase generada por el wsdl2java donde pongo mi código para implementar el servicio. Ese componente tiene los métodos del servicio y es donde puedo ponerle referencias a otros beans definidos en el application context, tales como DataSources, etc.

El archivo services.xml que mencioné anteriormente, debe estar dentro del META-INF del AAR. Y pues hay que empacar varios jars en lib como mencioné anteriormente. Para esto se puede modificar el script de ant; yo preferí hacer un script para construir todo con Gradle, porque eso me permite manejar las dependencias para compilar, ejecutar pruebas, y empacar el AAR.

Compilar, probar, empacar: con Gradle es sencillo

Para que esto funcionara de manera sencilla, primero acomodé todo en el layout estándar para proyectos de Gradle: dentro del directorio raíz del proyecto puse las cosas de esta forma:

src/main/groovy
Aquí va todo el código fuente que yo voy a hacer para el proyecto, junto con los archivos Java generados por el wsdl2java
src/main/resources
Aquí va el application context
src/main/resources/META-INF
Aquí va el services.xml
src/test/groovy
Aquí van mis pruebas, que hice con Spock
src/test/resources
Aquí puedo poner un application context de prueba con mockups o componentes configurados de forma distinta al real

Y necesito por supuesto un build.gradle, que va más o menos así:

apply plugin:'groovy'

//Agregamos una configuración extra para empacar el AAR
//Sirve para definir como dependencias los jars que queremos
//agregar en el lib del AAR
configurations {
  packAAR
}

dependencies {
  compile 'org.apache.axis2:axis2-adb:1.6.2',
    'org.codehaus.groovy:groovy:2.0.8:indy',
    'org.springframework:spring-context:3.2.2.RELEASE',
    'org.slf4j:slf4j-api:1.7.5'
  testCompile 'org.codehaus.groovy:groovy:2.0.8:indy',
    'org.spockframework:spock-spring:0.7-groovy-2.0'
  packAAR 'org.codehaus.groovy:groovy:2.0.8:indy',
    'org.slf4j:slf4j-log4j12:1.7.5',
    'org.springframework:spring-context:3.2.2.RELEASE'
}

jar {
  extension='aar'
  into('lib') {
    from configurations.packAAR
  }
}

Es un script de Gradle bastante simple; lo único que tiene fuera de lo común es la configuración packAAR (que puede llamarse como sea), la cual hice para definir como dependencias ahí los jars que quiero incluir en el AAR, y tuve que modificar la configuración del jar para incluir esas dependencias y que se metan al directorio lib.

Cuando ejecuto gradle build, al final me queda un archivo con extensión aar dentro de build/libs, el cual simplemente debo meter en axis2/WEB-INF/services (si uso Tomcat, esa ruta está dentro de webapps una vez que arrojé el axis2.war ahí mismo) para publicar mi servicio.

El archivo AAR contendrá los jars de Spring, log4j, slf4j, y cualquier otra cosa que defina (directa o indirectamente) como dependencia de la configuración packAAR en el script de Gradle. Es necesario repetir algunas cosas que están en la configuración de compilación, pero es que el AAR no debe incluir las bibliotecas de Axis2 porque esas ya están en el axis2.war.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Muy buena explicacion

Yo apenas realice un Servicio WEB usando Spring y Apache CXF, me parecio de lo mas practico y facil de implementar, de hecho para probarlo puedes usar el Test de Spring y el WSDL lo genera al vuelo usando las interfaces.

Saludos

Imagen de ezamudio

WSDL

El problema aquí es que no podía hacer el web service como a mí se me ocurriera sino que el cliente ya me dio un WSDL porque ellos ya tienen hecho su desarrollo y solamente quieren invocar el web service que tengo que implementar, con su cliente que ya está hecho y no le van a hacer modificaciones. Por lo tanto tengo que apegarme al formato de los datos que ya tienen; por eso usé Axis2, porque el wsdl2java te genera los componentes para el servicio a partir del archivo WSDL.

Cuando voy a hacer un web service con el formato que yo quiera, simplemente diseño el componente, le pongo las anotaciones de @WebMethod y demás, y lo publico con el SimpleJaxWsServiceExporter de Spring, es lo más simple que he encontrado.

OK

Siendo asi, pues ni hablar. Creo que con CXF tambien puedes armar tus servicios a partir de un WSDL, no estoy seguro pero lo voy a investigar. También voy a checar si AXIS 2 me ofrece mejor rendimiento que CXF, ya que segun se CXF implementa la interface Future para el manejo de Hilos y concurrencia.

Saludos.

Imagen de ingscjoshua

Y con java 6??

Y ya intentaron hacer un cliente con java 6??

Imagen de ezamudio

Hacer un cliente con java 6 es muy fácil. Alguien lo puso en su blog aquí hace mucho tiempo. Pero nuevamente: yo aquí estoy hablando de publicar un servicio, a partir de un WSDL, no de hacer un cliente.

Ayuda

ezamudio pilas ayudame con mi blog aqui te lo dejo Problemas con jframe al momento de maximizarlo

Imagen de jsmaster

Axis y SAP WS

Si, realmente axis me parece de los mas simple, cierto día me toco implementar WS que me entreganban, a los cuales tenia acceso al SAP, y me impresionó la simplicidad con la que se manipula.