Servicios Web SOAP con JAX-WS, Spring y CXF (I) : Servidor

Última actualización: 03/03/2019

logo java

Java API for XML Web Services (JAX-WS), es una especificación para la creación de servicios web basados en XML-SOAP que vino a suponer la evolución de la especificación JAX-RPC y que forma parte de JEE 5 y Java SE 6. Está basada en anotaciones y permite la creación tanto de servicios como de clientes de servicios web SOAP directamente con clases Java POJO sin tener que lidiar con XML lo que simplifica enormemente trabajar con esta tecnología.

JAX-WS cuenta con una implementación de referencia llamada Metro, aunque también existen otros productos que implementan la especificación tales como CXF (el antiguo XFire) o Axis 2. En este tutorial vamos a partir de la base de que ya tenemos una aplicación web que utiliza Spring (al menos su contenedor IoC) y queremos publicar los métodos de un bean como un servicio web SOAP estándar para su consumo por parte de terceras aplicaciones. El módulo Spring WS permite la creación de servicios web Contract-first y un soporte (limitado) del estándar JAX-WS pero lo que nosotros queremos es generar el servicio web automáticamente a partir de nuestro código Java sin definir el WSDL.

Este es el primero de una serie de tutoriales dedicados a JAX-WS con CXF y Spring.

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

Requisitos: Conocimientos básicos de Spring IoC, aplicaciones web Java, Maven y servicios SOAP

Resumen

Los pasos a seguir y que detalla el artículo son los siguientes

  1. Incluir las dependencias de Spring y CXF
  2. Configurar en el web.xml los servlets de Spring y CXF
  3. Si utilizamos log4j, configurar la redirección de los logs de CXF
  4. Anotar con @WebService la interfaz o clase cuyos métodos públicos se publicarán con SOAP
  5. En el applicationContext definir el endpoint para la clase/interfaz
  6. El WS queda disponible dentro de la aplicación en la ruta / [url-pattern de CXF en el web.xml] / [endpoint del WS en el applicationContext]

Proyecto para pruebas

Vamos a crear un proyecto web Java estándar con Maven que, tal y como veremos más adelante, consistirá únicamente en un bean de Spring que expondremos como un servicio web. Se utilizará Spring 4.3.22 y CXF 3.2.8, y para este último hay que añadir un par de dependencias: cxf-rt-frontend-jaxws y cxf-rt-transports-http

<?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>spring-cxf-ws</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <description>Maven + JAX-WS + Spring + CXF web 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>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
        <log4j.version>1.2.17</log4j.version>
        <cxf.version>3.2.8</cxf.version>
        <spring.version>4.3.22.RELEASE</spring.version>
        <maven.compiler.plugin.version>3.8.0</maven.compiler.plugin.version>
        <maven.war.plugin.version>3.2.2</maven.war.plugin.version>
    </properties>

    <build>

        <pluginManagement>

            <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-war-plugin</artifactId>
                    <version>${maven.war.plugin.version}</version>
                    <configuration>
                        <archive>
                            <manifestEntries>
                                <Implementation-Version>${project.version}
                                </Implementation-Version>
                                <Implementation-Title>${artifactId}</Implementation-Title>
                                <Extension-Name>${artifactId}</Extension-Name>
                                <Built-By>Daniel Medina - danielme.com</Built-By>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>

            </plugins>

        </pluginManagement>

    </build>

    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <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>

En el web.xml y utilizando la especificación Servlet 3.1 (requiere Tomcat 8), hacemos la siguiente configuración:

  • Spring: Se inicia el contexto de Spring como en cualquier proyecto web con Spring IoC indicando los xml de configuración.
  • CXF: Iniciamos su servlet, indicando la ruta relativa desde la que “colgaremos” nuestros servicios CXF. He utilizado /ws
<?xml version="1.0" encoding="UTF-8"?>

<web-app id="spring-cxf-ws"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">

    <display-name>spring-cxf-ws</display-name>

    <!-- ==============Spring============= -->

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- ==============CXF============= -->

    <servlet>
        <servlet-name>cxf</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>cxf</servlet-name>
        <url-pattern>/ws/*</url-pattern>
    </servlet-mapping>

</web-app>

El fichero log4j.xml tendrá una configuración estándar en modo DEBUG para salida por consola y fichero.

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

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!-- http://wiki.apache.org/logging-log4j/Log4jXmlFormat -->

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %C:%L - %m%n" />
		</layout>
	</appender>

	<appender name="file" class="org.apache.log4j.RollingFileAppender">
		<param name="file" value="logs/logger.log" />
		<param name="MaxFileSize" value="5MB" />
		<param name="MaxBackupIndex" value="10" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%d{MM-dd-yyyy hh:mm:ss,SSS a} %5p %C:%L - %m%n" />
		</layout>

	</appender>

	<root>
		<priority value="debug" />
		<appender-ref ref="console" />
		<!--<appender-ref ref="file" />-->
	</root>

</log4j:configuration>

Para redireccionar los logs de CXF a log4j se creará el fichero de texto /src/main/resources/META-INF/org.apache.cxf.Logger que contendrá la siguiente línea: org.apache.cxf.common.logging.Log4jLogger

org.apache.cxf.common.logging.Log4jLogger

El applicationContext.xml de momento lo dejamos “vacío” pero con lo que necesitaremos.

<?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">

        <import resource="classpath:META-INF/cxf/cxf.xml"/>
		<import resource="classpath:META-INF/cxf/cxf-extension-xml.xml"/>
		<import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
	
</beans>

Desplegando en Tomcat 8, podemos comprobar si nuestra aplicación está operativa llamando a la url en la que se encuentran los servicios CXF, que en nuestro caso devolverá lo siguiente:

no services have been found

Creación del servicio web

El servicio web SOAP que se va a crear permitirá realizar ciertas operaciones con los jugadores de un equipo de fútbol. Este servicio web será en realidad un bean definido en Spring que implementará una interfaz y si bien ninguno de estos requisitos son obligatorios (podríamos publicar cualquier clase y esta no tiene que implementar una interfaz) es la práctica habitual. Además normalmente vamos a querer inyectar en el bean clases de servicio, DAOs, etc para implementar las funcionalidades. La clase para modelar los jugadores es la siguiente:

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

/**
 * 
 * @author danielme.com
 *
 */
public class Player 
{
	private String name;
	
	private int age;
	
	private int number;
	
	public Player()
	{
		super();
	}
	
	public Player(int number, String name, int age)
	{
		super();
		this.name = name;
		this.age = age;
		this.number = number;
	}
	
	@Override
	public String toString() 
	{
	   return number + " - " + name + " (" + age + ")";	
	}

        getter y setters...

}

Y ahora vamos con la interfaz que define las operaciones sobre los jugadores

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);
	
	void foo();
}

Obsérvese que para convertirla en la definición de un servicio web se ha anotado con @WebService. La configuración por defecto es suficiente, pero se pueden modificar ciertos parámetros a la hora de generar el WSDL y los mensajes SOAP correspondientes tal y como podemos ver en la documentación. El método foo() no hará nada, y luego veremos por qué se ha definido.

Cursos de programación

Por omisión, todos los métodos de la interfaz serán publicados como servicio web. Usaremos la configuración por defecto, pero se pueden personalizar un poco los métodos y sus parámetros, veamos un ejemplo:

@WebMethod(operationName="actualizar")
@WebResult(name="booleano", targetNamespace="http://com.danielme.demo.resultado")
boolean updatePlayerByNumber(int number,  @WebParam(header = true, name = "jugador", targetNamespace = "http://com.danielme.demo.jugador")Player player);
  • @WebMethod: personaliza la representación del método en el SOAP. En nuestro caso, establecemos el nombre de wsdl:operation
  • @WebResult: permite personalizar el valor wsdl:part correspondiente con el valor devuelto por el método. En nuestro ejemplo, le hemos puesto el nombre booleano y un namespace propio
  • @WebParam: permite personalizar el valor wsdl:part correspondiente a los parámetros que debe recibir el método. En nuestro ejemplo, le hemos definido el nombre, el namespace e indicamos que el valor “viajará” en el header del documento SOAP

Ahora vamos con la implementación de esta interfaz. Es un bean de Spring definido mediante anotación y que implementa la interfaz ITeamService. Con la anotación @BindingType establecemos que queremos utilizar el protocolo SOAP 1.2 (por defecto se utiliza la versión 1.1).

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


import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;


import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import javax.xml.ws.BindingType;

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

@Service(value="teamService")
@BindingType(javax.xml.ws.soap.SOAPBinding.SOAP12HTTP_BINDING)
public class TeamServiceImpl implements ITeamService {

    private static final Logger logger = Logger.getLogger(TeamServiceImpl.class);

    private final List<Player> team;

    public TeamServiceImpl() {
        team = new LinkedList<Player>();
        team.add(new Player(1, "David De Gea", 22));
        team.add(new Player(2, "Rafael", 22));
        team.add(new Player(3, "Patrice Evra", 31));
        team.add(new Player(4, "Phil Jones", 21));
        team.add(new Player(5, "Rio Ferdinand", 34));
        team.add(new Player(7, "Antonio Valencia", 27));
        team.add(new Player(8, "Anderson", 24));
        team.add(new Player(10, "Wayne Rooney", 27));
    }

    @Override
    public List<Player> getTeam() {       
        logger.info("team requested");
        return team;
    }

    @Override
    public List<Player> getPlayers(int... numbers) {
        List<Player> result = new LinkedList<Player>();

        if (numbers != null) {
            Player player = null;
            for (int i = 0; i < numbers.length; i++) {
                player = findById(numbers[i]);
                if (player != null) {
                    result.add(player);
                }
            }
        }
        logger.info("returning " + result.size() + " players");

        return result;
    }

    @Override
    public boolean updatePlayerByNumber(int number, Player player) {
        logger.info("updating player " + number);

        Player playerOld = findById(number);
        boolean result = false;
        if (playerOld != null) {
            playerOld.setAge(player.getAge());
            playerOld.setNumber(player.getNumber());
            playerOld.setName(player.getName());
            result = true;
        } else {
            logger.warn("player " + number + " not found");
        }
        return result;

    }

    @Override
    public boolean deletePlayer(int number) {
        logger.info("deleting player " + number);

        Player player = findById(number);
        boolean result = false;

        if (player != null) {
            team.remove(player);
            result = true;
        } else {
            logger.warn("player " + number + " not found");
        }
        return result;
    }

    @Override
    @WebMethod(exclude = true)
    public void foo() {
        logger.info("Hello world!!!");
    }

    private Player findById(int number) {
        for (Player player : team) {
            if (player.getNumber() == number) {
                return player;
            }
        }
        return null;
    }

Ya sólo resta configurar el servicio en el applicationContext.xml de Spring. He añadido comentada cómo habría que hacer la configuración si queremos exponer como web service una clase cualquiera que no fuera un bean de Spring (si esta clase no implementa interfaz alguna las anotaciones de JAX-WS irían directamente en la misma). El resultado es el siguiente (no olvidar tampoco “activar” las anotaciones para la inyección de dependencias), obsérvese que al haber definido al menos un servicio podemos prescindir de los import de configuración de CXF que vimos anteriormente.

<?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">

	<!-- activates annotations (@Service, @Component, @Autowired) -->
	<context:component-scan base-package="com.danielme.demo" />

    <!-- publish the web service -->
	<!--  <jaxws:endpoint id="teamService" implementor="com.danielme.demo.jaxws.cxf.ws.TeamServiceImpl" address="/v/1/teamService" />-->

	<!-- references a spring bean  -->
	<!--  <bean id="teamService" class="com.danielme.demo.jaxws.cxf.ws.TeamService"/>--> 
	<jaxws:endpoint id="teamServiceWS" implementor="#teamService" address="/v/1/teamService"/>
	
</beans>

Se ha indicado el bean de Spring que hay que exponer mediante SOAP y su ruta en el servidor que será relativa a la ruta en la que se ha configurado CXF en el web.xml. Con esto ya lo tenemos todo listo y podemos desplegar la aplicación para comprobar que nuestro servicio está operativo. Si accedemos a la url en la que “escucha” el servlet de CXF veremos un listado de todos los servicios desplegados.

SOAP 1

Nota: se pueden ocultar los servicios mostrados en el listado.

<jaxws:endpoint id="teamServiceWS" implementor="#teamService" address="/v/1/teamService">
	<jaxws:properties><entry key="org.apache.cxf.endpoint.private" value="true"/></jaxws:properties>
</jaxws:endpoint>

En ocasiones puede ser muy útil activar el logging de CXF para cierto endpoint.

<jaxws:endpoint id="teamServiceWS"
        implementor="#teamService" address="/v/1/teamService">       
        <jaxws:features>
            <bean class="org.apache.cxf.feature.LoggingFeature" />
        </jaxws:features>
</jaxws:endpoint>

Los detalles de las peticiones SOAP se imprimen tanto al recibirse como al enviarse.

---------------------------
ID: 1
Response-Code: 200
Encoding: UTF-8
Content-Type: text/xml
Headers: {}
Payload: 22David De Gea122Rafael231Patrice Evra321Phil Jones434Rio Ferdinand527Antonio Valencia724Anderson827Wayne Rooney10
--------------------------------------

El descriptor de nuestro servicio que CXF ha generado “mágicamente” lo encontramos en la url

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

<wsdl:definitions name="TeamServiceImplService"
	targetNamespace="http://ws.cxf.jaxws.demo.danielme.com/">
	<wsdl:types>
		<xs:schema elementFormDefault="unqualified"
			targetNamespace="http://ws.cxf.jaxws.demo.danielme.com/" version="1.0">
			<xs:element name="deletePlayer" type="tns:deletePlayer" />
			<xs:element name="deletePlayerResponse" type="tns:deletePlayerResponse" />
			<xs:element name="foo" type="tns:foo" />
			<xs:element name="fooResponse" type="tns:fooResponse" />
			<xs:element name="getPlayers" type="tns:getPlayers" />
			<xs:element name="getPlayersResponse" type="tns:getPlayersResponse" />
			<xs:element name="getTeam" type="tns:getTeam" />
			<xs:element name="getTeamResponse" type="tns:getTeamResponse" />
			<xs:element name="updatePlayerByNumber" type="tns:updatePlayerByNumber" />
			<xs:element name="updatePlayerByNumberResponse" type="tns:updatePlayerByNumberResponse" />
			<xs:complexType name="deletePlayer">
				<xs:sequence>
					<xs:element name="arg0" type="xs:int" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="deletePlayerResponse">
				<xs:sequence>
					<xs:element name="return" type="xs:boolean" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="getPlayers">
				<xs:sequence>
					<xs:element maxOccurs="unbounded" minOccurs="0" name="arg0"
						type="xs:int" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="getPlayersResponse">
				<xs:sequence>
					<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
						type="tns:player" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="player">
				<xs:sequence>
					<xs:element name="age" type="xs:int" />
					<xs:element minOccurs="0" name="name" type="xs:string" />
					<xs:element name="number" type="xs:int" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="foo">
				<xs:sequence />
			</xs:complexType>
			<xs:complexType name="fooResponse">
				<xs:sequence />
			</xs:complexType>
			<xs:complexType name="updatePlayerByNumber">
				<xs:sequence>
					<xs:element name="arg0" type="xs:int" />
					<xs:element minOccurs="0" name="arg1" type="tns:player" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="updatePlayerByNumberResponse">
				<xs:sequence>
					<xs:element name="return" type="xs:boolean" />
				</xs:sequence>
			</xs:complexType>
			<xs:complexType name="getTeam">
				<xs:sequence />
			</xs:complexType>
			<xs:complexType name="getTeamResponse">
				<xs:sequence>
					<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
						type="tns:player" />
				</xs:sequence>
			</xs:complexType>
		</xs:schema>
	</wsdl:types>
	<wsdl:message name="getPlayers">
		<wsdl:part element="tns:getPlayers" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="fooResponse">
		<wsdl:part element="tns:fooResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="getTeamResponse">
		<wsdl:part element="tns:getTeamResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="updatePlayerByNumber">
		<wsdl:part element="tns:updatePlayerByNumber" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="foo">
		<wsdl:part element="tns:foo" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="getPlayersResponse">
		<wsdl:part element="tns:getPlayersResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="updatePlayerByNumberResponse">
		<wsdl:part element="tns:updatePlayerByNumberResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="deletePlayer">
		<wsdl:part element="tns:deletePlayer" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="getTeam">
		<wsdl:part element="tns:getTeam" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:message name="deletePlayerResponse">
		<wsdl:part element="tns:deletePlayerResponse" name="parameters">
		</wsdl:part>
	</wsdl:message>
	<wsdl:portType name="ITeamService">
		<wsdl:operation name="deletePlayer">
			<wsdl:input message="tns:deletePlayer" name="deletePlayer">
			</wsdl:input>
			<wsdl:output message="tns:deletePlayerResponse" name="deletePlayerResponse">
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="getPlayers">
			<wsdl:input message="tns:getPlayers" name="getPlayers">
			</wsdl:input>
			<wsdl:output message="tns:getPlayersResponse" name="getPlayersResponse">
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="foo">
			<wsdl:input message="tns:foo" name="foo">
			</wsdl:input>
			<wsdl:output message="tns:fooResponse" name="fooResponse">
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="updatePlayerByNumber">
			<wsdl:input message="tns:updatePlayerByNumber" name="updatePlayerByNumber">
			</wsdl:input>
			<wsdl:output message="tns:updatePlayerByNumberResponse"
				name="updatePlayerByNumberResponse">
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="getTeam">
			<wsdl:input message="tns:getTeam" name="getTeam">
			</wsdl:input>
			<wsdl:output message="tns:getTeamResponse" name="getTeamResponse">
			</wsdl:output>
		</wsdl:operation>
	</wsdl:portType>
	<wsdl:binding name="TeamServiceImplServiceSoapBinding"
		type="tns:ITeamService">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="deletePlayer">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="deletePlayer">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="deletePlayerResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="getPlayers">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="getPlayers">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="getPlayersResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="foo">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="foo">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="fooResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="updatePlayerByNumber">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="updatePlayerByNumber">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="updatePlayerByNumberResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
		<wsdl:operation name="getTeam">
			<soap:operation soapAction="" style="document" />
			<wsdl:input name="getTeam">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="getTeamResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>
	<wsdl:service name="TeamServiceImplService">
		<wsdl:port binding="tns:TeamServiceImplServiceSoapBinding"
			name="TeamServiceImplPort">
			<soap:address
				location="http://localhost:8080/spring-cxf-ws/ws/v/1/teamService" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

Tal y como vemos todos los métodos de la interfaz se han publicado dentro del servicio web, ¿pero qué pasaría si no queremos exponer algún método? Puede ser el caso de que nuestro servicio de Spring sólo deba publicar al exterior un subconjunto de funcionalidades. Pues bien, vamos a evitar que se publique el método foo, y para ello anotamos el método de la siguiente forma siempre en la implementación y no en la interfaz:

@Override
@WebMethod(exclude=true)
public void foo(){
   logger.info("Hello world!!!");
}

Si volvemos a desplegar la aplicación con este cambio podemos comprobar que ahora el método foo ya no forma parte de nuestro servicio web.

available soap services

Ya tenemos listo nuestro servicio de Spring para ser consumido por otras aplicaciones. En la segunda parte del artículo veremos lo fácil que resulta hacerlo con JAX-WS, CXF y, opcionalmente, Spring. Para interactuar con servicios web SOAP utilizo habitualmente la herramienta Soap UI. Con la opción File->New SOAP Project creamos un proyecto para un WSDL y marcamos la opción “Create sample requests for all operations”.

En el navegador de proyectos tendremos para cada operación una petición para poder probar el servicio.

Algunas limitaciones

La “magia” de JAX-WS está limitada por la tecnología subyacente, esto es, los mensajes XML SOAP. Esto nos obliga a tener cuidado con los servicios que publicamos, por ejemplo:

  • Los parámetros utilizados no deben tener referencias cruzadas, esto es, constituir una estructura de datos recursiva. Esto es debido a que el mensaje de respuesta del WS es un XML en el que no se pueden representar estos datos. Si incurrimos en este error, se producirá un excepción en el servidor que recibiremos en el cliente con un mensaje que, afortunadamente, es bastante descriptivo y nos ayudará a identificar exactamente cuál es el error producido. Este mensaje comenzará más o menos así:
    org.apache.cxf.interceptor.Fault: Marshalling Error: A cycle is detected in the object graph. This will cause infinitely deep XML
  • Si queremos que el servicio sea totalmente estándar para usarlo con cualquier cliente/lenguaje, debemos utilizar como estructura de datos únicamente Arrays (o en su defecto List que son correctamente mapeados por CXF) en lugar de tipos propios de Java como HashMap o similares.

Código completo

El proyecto completo se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Un comentario sobre “Servicios Web SOAP con JAX-WS, Spring y CXF (I) : Servidor

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.