Servicios Web SOAP con JAX-WS, Spring y CXF (II): Clientes

Última actualización: 03/03/2019

logo java

Tras la primera parte del artículo ya tenemos una aplicación web que publica mediante SOAP un bean de Spring de forma muy sencilla gracias a JAX-WS y Apache CXF. Ahora toca volver a utilizar estas tecnologías pero con el objetivo contrario, esto es, consumir un servicio web que en nuestro caso será el del primer artículo. Lo haremos con un proyecto Maven y una clase Main y, opcionalmente, con Spring.

  1. Servidor
  2. Clientes
  3. Securización TLS + BASIC
  4. Handlers

Sin Spring (cxf-client)

Usaremos un proyecto Maven estándar no web con el siguiente pom.xml con las únicas dependencias de CXF y log4j

<?xml version="1.0" encoding="UTF-8" ?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.danielme.demo</groupId>
    <artifactId>cxf-client</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <description>Maven + JAX-WS + CXF client project just for playing
		around with SOAP web services
	</description>
    <inceptionYear>2013</inceptionYear>

    <licenses>
        <license>
            <name>GNU GENERAL PUBLIC LICENSE, Version 3.0</name>
            <url>http://www.gnu.org/licenses/gpl.html</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>danielme.com</id>
            <name>Daniel Medina</name>
        </developer>
    </developers>


    <properties>
        <cxf.version>3.2.8</cxf.version>
        <log4j.version>1.2.17</log4j.version>
        <maven.compiler.plugin.version>3.8.0</maven.compiler.plugin.version>
        <maven.jar.plugin.version>3.1.1</maven.jar.plugin.version>
        <maven.exec.plugin.version>1.6.0</maven.exec.plugin.version>
        <java.version>1.7</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.mainClass>com.danielme.demo.jaxws.cxf.client.Main</project.build.mainClass>
    </properties>

    <build>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>${maven.jar.plugin.version}</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>${project.build.mainClass}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <!-- mvn clean package exec:java -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>${maven.exec.plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>${project.build.mainClass}</mainClass>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

    </dependencies>

</project>

El cliente que queremos será una clase proxy que se generará en tiempo de ejecución con la factoría JaxWsProxyFactoryBean. Para ello, la clave es tener una interfaz que replique los métodos del servicio web con las anotaciones correspondientes, así como las distintas clases que se utilicen en los mismos. En nuestro caso, basta con incluir en nuestro proyecto tanto la interfaz ITeamService como la clase Player de la primera parte del artículo respetando exactamente la misma ruta de paquetes ya que es la que hemos usado (configuración por defecto) para establecer los namespace de nuestro servicio. Es más, de la interfaz es suficiente con incluir sólo aquellos métodos que vayamos a utilizar, por lo que obviaremos el método foo que excluimos del servicio web. La interfaz queda prácticamente igual:

package com.danielme.demo.jaxws.cxf.ws;

import java.util.List;

import javax.jws.WebService;

import com.danielme.demo.jaxws.cxf.model.Player;

/**
 * 
 * @author danielme.com
 *
 */
@WebService
public interface ITeamService 
{	
	List<Player> getTeam();
	
	List<Player> getPlayers(int... numbers);
	
	boolean updatePlayerByNumber(int number, Player player);
	
	boolean deletePlayer(int number);

}

El otro elemento que vamos a necesitar es la ruta en la que se encuentra el descriptor del servicio web, la vamos a definir fuera del código en un fichero de propiedades. Esta es una buena práctica que evita tener que recompilar la aplicación si queremos cambiar la url y la hemos puesto en el código.

endpoint = http://localhost:8080/spring-cxf-ws/ws/v/1/teamService?wsdl

cxf-client

En la imagen vemos un Main en el que se va a consumir nuestro servicio web y que probará todos los métodos disponibles. El código resaltado es aquel que crea el cliente. Para que todo funciona la aplicación servidor debe estar en ejecución y escuchando en la url definida en el config.properties.

package com.danielme.demo.jaxws.cxf.client;

import java.util.List;
import java.util.Properties;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.log4j.Logger;

import com.danielme.demo.jaxws.cxf.model.Player;
import com.danielme.demo.jaxws.cxf.ws.ITeamService;

/**
 * 
 * @author danielme.com
 *
 */
public class Main 
{

	public static final Logger logger = Logger.getLogger(Main.class);

	public static void main(String[] args) throws Exception
	{
		Properties properties = new Properties();
		properties.load(Main.class.getClassLoader().getResourceAsStream("config.properties"));
		
		//client
		JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
		jaxWsProxyFactoryBean.setServiceClass(ITeamService.class);
		jaxWsProxyFactoryBean.setAddress(properties.getProperty("endpoint"));
		ITeamService teamServiceClient = (ITeamService) jaxWsProxyFactoryBean.create();
		
		//test
		logger.info("getTeam");		
		List<Player> team = teamServiceClient.getTeam();
		for (Player player : team)
		{
			logger.info("       " + player.getNumber() + " : " + player.getName() + " (" + player.getAge() +")");
		}		
		
		logger.info("\n updatePlayerByNumber");
		teamServiceClient.updatePlayerByNumber(1, new Player(1,"Anders Lindegaard", 28));
		
		logger.info("\n deletePlayer");
		teamServiceClient.deletePlayer(6); 
		
		logger.info("\n getPlayers");
		team = teamServiceClient.getPlayers(1,3,6);		
		for (Player player : team)
		{
			logger.info("       " + player.getNumber() + " : " + player.getName() + " (" + player.getAge() +")");
		}
	}

}

El resultado de la ejecución nos permitirá validar el correcto funcionamiento del ejemplo completo (servicio y cliente):

resultado

Por último, comentar que la creación del cliente lleva algo de tiempo (aunque estemos hablando de milisegundos) por lo que por motivos de eficiencia conviene crearlo una única vez y utilizar siempre la misma instancia (el patrón de diseño singleton).

Con Spring (spring-cxf-client)

Vamos a rehacer el ejemplo anterior para que utilice el contenedor de Spring si es que lo estamos usando en nuestra aplicación (tal y como acabamos de ver para cliente sólo necesitamos Apache CXF). La tarea es sencilla puesto que lo único que habrá que hacer es añadir las dependencias correspondientes al pom.xml y llevarnos la creación del cliente del código al applicationContext.xml

Sólo tenemos que añadir una dependencia.

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.22.RELEASE</version>
		</dependency>

Usaremos el siguiente applicationContext.xml. Para más información sobre la utilización de ficheros de propiedades en Spring consultar el tutorial Ficheros de propiedades en Spring.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="http://www.springframework.org/schema/beans  
		http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  
		http://www.springframework.org/schema/context  
		http://www.springframework.org/schema/context/spring-context-4.3.xsd  
		http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

<context:property-placeholder location="classpath:config.properties" />

<jaxws:client id="teamServiceClient"
              serviceClass="com.danielme.demo.jaxws.cxf.ws.ITeamService"
              address="${endpoint}" />
	
</beans>

Nuestro cliente ya es un bean de Spring (y un singleton por defecto), por lo que en la clase Main los únicos cambios a realizar serán iniciar programáticamente el contenedor de Spring y recuperar el bean deseado. La clase completa quedará tal que así, y el resultado final de su ejecución deberá ser el mismo que en el ejemplo anterior.

Nota: puesto que el cliente modifica los datos que proporciona el servidor, si se quiere que el resultado sea siempre el mismo se deberá reiniciar el servidor cada vez que se ejecute el cliente.

package com.danielme.demo.jaxws.cxf.client.spring;

import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;


import com.danielme.demo.jaxws.cxf.model.Player;
import com.danielme.demo.jaxws.cxf.ws.ITeamService;

public class Main 
{
	public static final Logger logger = Logger.getLogger(Main.class);


	public static void main(String[] args) throws Exception
	{		
		//Initializes Spring Container 
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");		
		ITeamService teamServiceClient = (ITeamService) applicationContext.getBean("teamServiceClient");		
		
		//test
		logger.info("getTeam");		
		List<Player> team = teamServiceClient.getTeam();
		for (Player player : team)
		{
			logger.info("       " + player.getNumber() + " : " + player.getName() + " (" + player.getAge() +")");
		}		
				
		logger.info("\n updatePlayerByNumber");
		teamServiceClient.updatePlayerByNumber(1, new Player(1,"Anders Lindegaard", 28));
				
				logger.info("\n deletePlayer");
				teamServiceClient.deletePlayer(6); 
				
				logger.info("\n getPlayers");
				team = teamServiceClient.getPlayers(1,3,6);		
				for (Player player : team)
				{
					logger.info("       " + player.getNumber() + " : " + player.getName() + " (" + player.getAge() +")");
				}
	}

}



Código de ejemplo
El código de ejemplo de las dos partes del artículo se encuentra en GitHub Para más información sobre cómo utilizar GitHub, consultar este artículo..

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.