Ejemplo sencillo de web service con jax-ws
Generar un web service con metro[editar]
Vamos a ver en este tutorial como hacer un pequeño Web Service con java usando jax-ws. jax-ws es la especificación para el desarrollo de web services y hay varias implementaciones posibles. Las más conocidas son Apache CXF y metro. En este ejemplo vamos a usar metros, así que lo primero que necesitamos, por supuesto, es descargarnos esa librería y despempaquetar el zip correspondiente en algún sitio. Aparte de toda la documentación y alguna herramientas que nos serán útiles más adelante, en el directorio lib tendremos todos los jar que necesitamos.
Montamos un proyecto nuevo en nuestro IDE favorito (eclipse, netbeans, ...) y añadimos al proyecto todas las librerías de ese directorio lib mencionado anteriormente
62.983 activation.jar 291.817 FastInfoset.jar 21.839 gmbal-api-only.jar 82.265 http.jar 104.554 jaxb-api.jar 876.738 jaxb-impl.jar 3.105.074 jaxb-xjc.jar 54.341 jaxws-api.jar 1.484.080 jaxws-rt.jar 520.641 jaxws-tools.jar 23.346 jsr173_api.jar 7.993 jsr181-api.jar 6.165 jsr250-api.jar 41.429 management-api.jar 38.772 mimepull.jar 156.212 policy.jar 68.177 resolver.jar 18.774 saaj-api.jar 288.529 saaj-impl.jar 11.001 stax-ex.jar 59.771 streambuffer.jar 505.825 woodstox.jar
Una vez hecho esto, sólo nos queda escribir nuestro código. No tenemos más que escribir la clase que será nuestro Web Service con sus métodos, que serán los accesibles desde la Web. Para indicar que la clase es un Web Service sólo tendremos que ponerle la anotación @WebService y a los métodos que queramos que sea accesibles la anotación @WebMethod. La clase sería la siguiente
package com.chuidiang.ejemplos.jax_ws; import javax.jws.WebMethod; import javax.jws.WebService; @WebService public class UnWebService { @WebMethod public float suma(float a, float b, UnDato c) { return a + b; } }
Podríamos compilar y generar un fichero .war con esta clase para desplegarla en un Tomcat o similar, pero podemos directamente ponerle un main() para hacerla ejecutable. Para hacer pública la clase UnWebService como Web Service podemos usar la clase EndPoint que viene con jax-ws. El código quedaría así
package com.chuidiang.ejemplos.jax_ws; import javax.jws.WebMethod; import javax.jws.WebService; import javax.xml.ws.Endpoint; @WebService public class UnWebService { @WebMethod public float suma(float a, float b, UnDato c) { return a + b; } public static void main(String[] args) { Endpoint.publish("http://localhost:8080/UnWebService", new UnWebService()); } }
Ahora sólo tenemos que compilarla como una clase normal y ejecutarla de la forma normal en java, con su método main(). Para ver que funciona, en el navegador debemos poner view-source:http://localhost:8080/sumador?wsdl que es la URL que hemos puesto en nuestro código y a la que hemos añadido detrás ?wsdl. Esto nos debería mostrar el wsdl generado por jax-ws para nuestro Web Service. Dependiendo del navegador (chrome en mi caso), para ver este fichero .wsdl, es posible que tengamos que decirle que queremos ver el código fuente de la página.
En el compilado de la clase es posible que nos salga algún error de compatibilidad de la librería jaxb y su versión. Se debe a que la versión que hemos bajado no es compatible con la que espera java. La forma de arreglarlo es compilar y ejecutar nuestro programa con la siguiente opción en la línea de comandos -Djava.endorsed.dirs=C:\LIBRERIAS\jaxws-ri\lib poniendo, por supuesto, el path que tú tengas para el directorio lib de jax-ws.
Generar el wsdl[editar]
Metro viene con una utilidad wsgen.bat que permite a partir de nuestra clase compilada UnWebService.class, obtener el wsdl correspondiente, así como algunas clases java correspondientes a los tipos definidos en ese wsdl, quizás útiles para llevarse a otra aplicación que necesite usar nuestro web service.
Lo primero que debemos hacer es poner en el PATH de búsqueda de ejecutables el directorio donde se encuentra ese fichero wsgen.bat. En mi caso, abriendo una ventana de ms-dos o línea de comandos, sería
set PATH=%PATH%;C:\Aplicaciones\jaxws-ri\bin
Ahora en la misma línea de comandos ejecutamos wsgen.bat. Debemos pasar como parámetros un classpath donde se encuentran la clases de nuestro web service, la clase del web service y alguna opción más que explicamos más abajo.
wsgen -cp C:\Users\chuidiang\workspaceJee\SERVIDOR_METRO\bin -d C:\Users\chuidiang\workspaceJee\SERVIDOR_METRO\wsdl com.chuidiang.ejemplos.jax_ws.UnWebService -wsdl -keep
- -cp es el classpath, que es donde nuestro IDE (eclipse, netbeans) deje los .class de nuestro proyecto. En mi caso, con eclipse, creando un proyecto java por defecto de nombre SERVIDOR_METRO, la ubicación de los .class es C:\Users\chuidiang\workspaceJee\SERVIDOR_METRO\bin
- -d C:\Users\chuidiang\workspaceJee\SERVIDOR_METRO\wsdl es el directorio donde queremos que wsgen nos deje el wsdl y las clases que genere. En mi caso cree en el proyecto eclipse un directorio wsdl y ahí ese es el path que he puesto.
- com.chuidiang.ejemplos.jax_ws.UnWebService Es el nombre de la clase con el web service, incluido el paquete de la misma.
- -wsdl indica que queremos que genere también el wsdl. Si no ponemos esta opción, sólo generará las clases java.
- -keep indica que queremos que mantenga los fuentes .java que genere wsgen. Si no ponemos esta opción, generará los fuentes java, los compilara y borrará los fuentes, dejándonos sólo los .class.
Una vez ejecutado el comando, en el directorio wsdl nos aparecerá el fichero UnWebService.wsdl, un .xsd con los tipos y una estructura de directorios com/chuidiang/ejemplos/jax_ws/jaxws en el que estarán dentro los java y class generados por la herramienta. Añade el último trozo del package .jaxws de su propia cosecha.
Hacer un cliente java de un web service[editar]
Una vez que tenemos el wsdl disponible, bien porque lo hemos generado, bien porque tenemos el web service arrancado y está público en una URL como http://localhost:8080/sumador?wsdl, tenemos en el directorio bin de metro una utilidad wsimport.bat que nos genera clases java de utilidad para codificar nuestro cliente.
Creamos nuestro proyecto de cliente con nuestro IDE favorito (en mi caso en eclipse lo he llamado METRO_CLIENTE, lo he creado en el directorio workspaeJee de eclipse y tiene un subdirectorio src para fuentes y otro subdirectorio wsdl creado manualmente para guardar el wsdl y xsd generado por wsgen en los pasos anteriores. Recuerda que debes añadir en el classpath de este poryecto los mismos .jar de metro que añadimos en el proyecto servidor.
C:\Users\chuidiang\workspaceJee\METRO_CLIENTE\src C:\Users\chuidiang\workspaceJee\METRO_CLIENTE\wsdl\UnWebServiceService.wsdl C:\Users\chuidiang\workspaceJee\METRO_CLIENTE\wsdl\UnWebServiceService_schema1.xsd
Para trabajar más fácilmente, desde un cmd de windows o línea de comandos, nos vamos al directorio C:\Users\chuidiang\workspaceJee\METRO_CLIENTE del proyecto y escribimos el siguiente comando
cd C:\Users\chuidiang\workspaceJee\METRO_CLIENTE wsimport -d src -keep wsdl\UnWebServiceService.wsdl
Las opciones de wsimport que usamos son:
- -d src para indicar en qué directorio queremos que nos genere los fuentes, es decir, el directorio src de nuestro proyecto eclipse.
- -keep para que no borre esos fuentes al terminar. Si no ponemos esta opción, creará los fuentes, los compilará para generar los .class y borrará los .java dejando los .class listos para su uso.
- wsdl\UnWebServiceService.wsdl path relativo y fichero wsdl. Si tenemos disponible el web service arrancado y el wsdl público a través de http, podríamos poner aquí la URL del wsdl de esta forma http://localhost:8080/UnWebService?wsdl. Es importante tener muy en cuenta de dónde sacamos el wsdl, ya que el código generado por wsimport buscará por defecto el web service en el sitio donde está el wsdl. Si ponemos un fichero, buscará el web service en el disco, con lo que no funcionará por defecto. Si usamos la URL real del web service, como http://undominio/unwebservice?wsdl, el código generado funcionará correctamente por defecto.
El resultado es que en nuestro directorio src aparecerán unas clases .java útiles para llamar al web service y también los .class compilados. Como vamos a trabajar con eclipse y por limpieza, deberíamos borrar los .class de esta ubicación, ya que eclipse normalmente los generará y los meterá en otro directorio propio de él (normalmente el directorio C:\Users\chuidiang\workspaceJee\METRO_CLIENTE\bin si dejamos las opciones por defecto).
Ahora sólo nos queda, en eclipse, hacer la clase main() con lo necesario
package com.chuidiang.ejemplos.metro; /** * Ejemplo simple de cliente de web service. Requiere para funcionar que el * servidor este arrancado. * * @author chuidiang */ public class Main { /** * @param args */ public static void main(String[] args) { // Obtencion del cliente. Sólo funciona si en wsimport usamos la URL // real del fichero wsdl, estilo http://dominioreal/unwebservice?wsdl UnWebServiceService unWebServiceService = new UnWebServiceService(); UnWebService unWebService = unWebServiceService.getUnWebServicePort(); // Ya podemos usarlo System.out.println(unWebService.suma(11.1, 22.2)); } }
Simplemente instanciamos la clase UnWebServiceService generada por wsimport y le pedimos el getUnWebServicePort() para obtener el UnWebService. Esta clase tiene los mismos métodos que la clase UnWebService del servidor, pero no los implementa, sino que redirige las llamadas al web service.
Una vez obtenida esa clase, sólo tenemos que llamar a sus métodos de forma normal.
Si en vez de usar la URL real del fichero wsdl hubiésemos usado un fichero u otra URL (localhost, por ejemplo), debemos instanciar la clase UnWebServiceService pasando como parámetros en el constructor la URL real del web service.
package com.chuidiang.ejemplos.main; import java.net.MalformedURLException; import java.net.URL; import javax.xml.namespace.QName; import com.chuidiang.ejemplos.metro.UnWebService; import com.chuidiang.ejemplos.metro.UnWebServiceService; /** * Ejemplo simple de cliente de web service. Requiere para funcionar que el * servidor este arrancado. * * @author chuidiang */ public class Main { /** * @param args * @throws MalformedURLException */ public static void main(String[] args) throws MalformedURLException { // Obtencion del cliente UnWebServiceService unWebServiceService = new UnWebServiceService( new URL("http://dominioreal:8080/UnWebService?wsdl"), // URL real del web service. new QName("http://metro.ejemplos.chuidiang.com/", // copiado del código generado por wsimport "UnWebServiceService")); UnWebService unWebService = unWebServiceService.getUnWebServicePort(); // Ya podemos usarlo System.out.println(unWebService.suma(11.1, 22.2)); } }
El segundo parámetro QName se puede copiar de dentro de la clase UnWebServiceService generada por wsimport, en la línea del constructor por defecto
public UnWebServiceService() { super(UNWEBSERVICESERVICE_WSDL_LOCATION, new QName( "http://metro.ejemplos.chuidiang.com/", "UnWebServiceService")); }
Ojito con las clases[editar]
Hay un detalle importante con el que hay que tener cuidado. La clase del web service (UnWebService en este caso) y todas las clases que intervengan en los parámetos de sus métodos o en el return (en este caso sólo son double en el método suma(), pero podrían ser otras clases que hayamos hecho nosotros), están ahora duplicadas. Por un lado tenemos las que hemos hecho a mano en el servidor, con la implementación real de estas clases (la que hace la suma de verdad) y por otro lado tenemos las del cliente, generadas por wsimport, que no implementan nada, salvo establecer conexión con el web service y delegar allí la suma. Es importante no mezclar ambas clases y saber que si estamos en el servidor debemos usar las del servidor, y si estamos en el cliente, las que se han generado con wsimport. Conviene por ello tener los dos proyectos (cliente y servidor) bien separados y sin dependencias entre ellos.