style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Web Services políglotas con Spring

Hace poco tuve que crear un web service para un cliente. Después de muchas vueltas y estar evaluando opciones, decidí usar el JAX-WS 2.1 que viene integrado en Java 6, cosa de lo cual me enteré hace tiempo gracias a willyxoft aquí mismo, junto con Spring para publicar el servicio, ya que lo tengo que poner en una aplicación JavaSE, como gateway hacia otro sistema; a fin de cuentas es solamente una traducción de protocolos, porque el cliente no quiere manejar un protocolo asíncrono y tenemos que darle un web service.

Dado que he estado incursionando un poco en el mundo de Groovy, quise implementar algunas partes de la aplicación usando este lenguaje. Y vaya que me ha ahorrado varias líneas de código... simplemente los beans que se usan como parámetros y valores de retorno en los métodos del web service, son bastante compactos, por ejemplo:

@XmlAccessorType(XmlAccessType.FIELD)
class Datos {
  String id
  String user
  String password
  String producto
  BigDecimal monto
  Date fecha
}

Si hubiera tenido que hacer ese bean en Java, para cada campo tendría que hacer algo así:

private String id;

public void setId(String value) {
  id = value;
}
public String getId() {
  return id;
}

Lo cual son 4 líneas de código más de lo que quiero teclear (contando líneas efectivas, es decir, descartando las que solamente son llave que cierra, líneas en blanco, etc).

Es importante ponerle esa anotación de acceso tipo FIELD al bean, porque por default intenta serializar por propiedades y se arroja una excepción de JAX al toparse con la metaClass de Groovy.

De hecho los componentes que hacen la lógica de invocar el protocolo asíncrono, también los hice en Groovy, aunque utilizo varias bibliotecas que fueron escritas en Java. Y finalmente, el componente que se publica como web service, ese sí lo hice en Java, sólo para evitarme cualquier sorpresa porque no sé realmente qué se le hace en tiempo de ejecución para publicarlo como web service, y porque simplemente es una clase que tiene referencias a los componentes que hacen el trabajo, con unos métodos que reciben un bean con los datos necesarios y devuelven otro bean con los datos del resultado, como el bean que mostré al principio. Este componente hay que decorarlo con varias anotaciones convertirlo en web service:

//Esta anotacion es para que se publique como web service
@WebService(name="servicio", serviceName="servicio")
//Esta anotacion es para que no se envuelvan los beans de parametros y resultados en otros beans que solamente son contenedores
@SOAPBinding(parameterStyle=SOAPBinding.ParameterStyle.BARE)
public class ServicioWeb {
  //Estos son los que realmente hacen el trabajo que necesito
  @Resource
  private ComponenteUno comp1;
  @Resource
  private ComponenteDos comp2;

  @WebMethod(action="op1")
  public Resultado operacion1(Datos datos) {
    return comp1.hacerAlgo(datos);
  }

  @WebMethod(action="op2")
  public OtroResultado operacion2(MasDatos datos) {
    return comp2.hacerOtraCosa(datos);
  }
}

Ya no pongo todas las clases, pero los beans Datos, MasDatos, Resultado y OtroResultado están hechos en Groovy como el primero que mostré.

Adicionalmente, pueden usar @WebResult(name="x") para decorar el valor de resultado, y también @WebMethod para los parámetros, lo que les permite definir la dirección de los mismos (por default son solamente de entrada) y su nombre.

Y ahora que ya tenemos el servicio hecho, hay que publicarlo. Como ya mencioné, esto es una aplicación JavaSE con Spring, de modo que tengo un ApplicationContext, así que solamente tengo que poner mis ComponenteUno y ComponenteDos en el contexto, poner también el ServicioWeb en el contexto, y usar el exportador de servicios web incluido en Spring, indicándole la dirección base donde quiero ponerlo.

<context:annotation-config />

<bean class="org.javamexico.ejemplo.ServicioWeb" />

<!-- Estos podria ponerlos como scripted beans porque estan hechos en Groovy,
     pero preferi compilarlos y meterlos como beans normales de Java -->
<bean class="org.javamexico.ejemplo.ComponenteUno" />
<bean class="org.javamexico.ejemplo.ComponenteDos" />

<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
        <property name="baseAddress" value="http://localhost:8080/" />
</bean>

El SimpleJaxWsServiceExporter inicia un servidor HTTP en el puerto 8080, analiza los otros beans en el application context y detecta que ServicioWeb tiene la anotación @WebService, y entonces lo publica ya como servicio, quedando accesible en

http://localhost:8080/servicio

En otras palabras, crea el endpoint para mi web service. De otra forma tendría que hacer algo como esto, que tampoco es muy complicado pero pues ya me lo ahorré, y el exportador de Spring puede crear endpoints para todos los web services que detecte en el application context.

Si quiero ver el WSDL del servicio, está en

http://localhost:8080/servicio?WSDL

y si quiero ver el esquema de datos como se van a exportar, está en

http://localhost:8080/servicio?xsd=1

Con el WSDL pueden crear un cliente usando Apache Axis o CXF, o el mismo JAX-WS como explica willyxoft o .NET o lo que gusten (ese es todo el chiste de los malditos web services a fin de cuentas).

Esto se ve (y es) muy sencillo, y qué bueno, porque solamente estamos exponiendo cierta funcionalidad de un sistema en forma de web service, pero pues todo el trabajo es realmente hacerlo... hay que recordar que los web services por HTTP (los únicos que se usan, realmente) son síncronos, es decir, el cliente abre la conexión HTTP, envía datos, y se queda esperando respuesta. Cuando el cliente envía datos, todo el tinglado de Spring y JAX-WS se encarga de recibirlos, interpretarlos, traducirlos y al final invocar el método correspondiente en nuestro componente publicado como web service; ahí se ejecuta nuestro código y cuando devolvemos el resultado en el return del método, ese bean se transforma a XML y se devuelve como una respuesta HTTP al cliente que la está esperando.

Algo que no me ha gustado de esto es que no he encontrado la forma de saber si el cliente se desconecta antes de recibir la respuesta, lo cual es importante para poder interrumpir la ejecución del método o hacer un rollback en caso que sea algo transaccional, etc. Si alguien sabe cómo recibir una notificación de JAX o del servidor HTTP en caso de desconexión prematura, por favor escriban aquí su comentario.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">