Cómo crear un servicio web SOAP en Java con Spring Boot
En este artículo, aprenderemos cómo crear un servicio web basado en SOAP utilizando Spring Boot Starter Web Services. A medida que las empresas buscan maneras más eficientes de comunicarse y compartir datos, los servicios web han tomado un rol central en la interacción entre sistemas. Los servicios web basados en SOAP son conocidos por su robustez y contratos bien definidos, y combinan perfectamente un enfoque de desarrollo de “contrato primero”. Si eres un programador Java en busca de fortalecer tus habilidades en la creación de servicios web, este tutorial te proporcionará una guía paso a paso.
Definición Básica
En términos sencillos, un servicio web es un servicio que permite la comunicación entre máquinas, sin depender de la plataforma que se utilice. SOAP (Simple Object Access Protocol) es un protocolo de mensajería que se basa en el intercambio de documentos XML sobre HTTP. A continuación, se detallan algunos de los conceptos esenciales relacionados con SOAP:
- Mensajes: Tanto las solicitudes como las respuestas son documentos XML.
- WSDL (Web Services Description Language): Proporciona un conjunto de reglas para definir los mensajes, los enlaces, las operaciones y la ubicación del servicio WSDL en Spring.
Debido a la complejidad del XML que se utiliza en SOAP, es recomendable utilizar frameworks como JAX-WS o Spring, como veremos en este tutorial.
Enfoques Disponibles
Existen dos enfoques principales al crear un servicio web:
- Contrato Atrasado (Contract-Last): Iniciamos con el código Java y generamos el contrato del servicio (WSDL) a partir de las clases.
- Contrato Primero (Contract-First): Comenzamos con el contrato WSDL, a partir del cual generamos las clases en Java.
Spring-WS solo admite el estilo de desarrollo “contrato primero”. Este enfoque es ventajoso porque permite definir claramente el servicio antes de implementarlo.
A continuación, procederemos a configurar nuestro proyecto Spring Boot para definir nuestro servidor de WS SOAP.
Comencemos añadiendo el spring-boot-starter-parent a nuestro proyecto:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>
Luego añadiremos el spring-boot-starter-web-services y wsdl4j:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
El enfoque “contrato primero” exige que primero creemos el dominio (métodos y parámetros) para nuestro servicio. Utilizaremos un archivo de esquema XML (XSD) que Spring-WS convertirá automáticamente en un WSDL.
Aquí está un ejemplo básico de un archivo XSD que define cómo se verán nuestras solicitudes y respuestas:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.baeldung.com/springsoap/gen"
targetNamespace="http://www.baeldung.com/springsoap/gen" elementFormDefault="qualified">
<xs:element name="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="tns:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="country">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="population" type="xs:int"/>
<xs:element name="capital" type="xs:string"/>
<xs:element name="currency" type="tns:currency"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="GBP"/>
<xs:enumeration value="EUR"/>
<xs:enumeration value="PLN"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
Este archivo define cómo se verá la solicitud getCountryRequest, que acepta un parámetro de tipo string, y la respuesta getCountryResponse, que contiene un objeto tipo country.
Generaremos ahora las clases Java del archivo XSD definido anteriormente. El jaxb2-maven-plugin se encargará de esto automáticamente en el momento de la construcción. Usaremos el plugin para compilar el archivo XSD en clases Java totalmente anotadas.
Agreguemos y configuremos el plugin en nuestro archivo pom.xml:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>src/main/resources/countries.xsd</source>
</sources>
<outputDirectory>src/main/java</outputDirectory>
<clearOutputDir>false</clearOutputDir>
</configuration>
</plugin>
Para generar las clases Java, ejecutamos el siguiente comando en la terminal:
mvn compile
La clase del endpoint del servicio SOAP manejará todas las solicitudes entrantes del servicio. Antes de definirlo, crearemos un repositorio Country para proporcionar datos al servicio web:
@Component
public class CountryRepository {
private static final Map<String, Country> countries = new HashMap<>();
@PostConstruct
public void initData() {
countries.put("Spain", new Country("Spain", 46704314, "Madrid", "EUR"));
// Inicializar otros países...
}
public Country findCountry(String name) {
return countries.get(name);
}
}
Ahora, definamos el endpoint:
@Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://www.baeldung.com/springsoap/gen";
private CountryRepository countryRepository;
@Autowired
public CountryEndpoint(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
@ResponsePayload
public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {
GetCountryResponse response = new GetCountryResponse();
response.setCountry(countryRepository.findCountry(request.getName()));
return response;
}
}
- @Endpoint: Registra la clase como un Endpoint del Servicio Web de Spring WS.
- @PayloadRoot: Define el método manejador según los atributos de namespace y localPart.
- @ResponsePayload: Indica que este método devuelve un valor que se mapea al payload de respuesta.
- @RequestPayload: Indica que este método acepta un parámetro que se mapea a la solicitud entrante.
Crearemos una clase para configurar el servlet del despachador de mensajes de Spring para recibir las solicitudes:
@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
// definiciones de beans
}
La anotación @EnableWs habilita las características del Servicio Web SOAP en esta aplicación Spring Boot. La clase WebServiceConfig extiende de la clase base WsConfigurerAdapter, que configura el modelo de programación de Spring-WS impulsado por anotaciones.
Ahora, crearemos un MessageDispatcherServlet, que se utiliza para manejar solicitudes SOAP:
@Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}
Configura el objeto ApplicationContext del servlet para que Spring-WS pueda encontrar otros beans de Spring y habilita la transformación de la ubicación del WSDL. Esto transforma el atributo location de soap:address en el WSDL de manera que refleje la URL de la solicitud entrante.
Finalmente, crearemos un objeto DefaultWsdl11Definition para exponer un WSDL 1.1 utilizando un XsdSchema:
@Bean(name = "countries")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("CountriesPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://www.baeldung.com/springsoap/gen");
wsdl11Definition.setSchema(countriesSchema);
return wsdl11Definition;
}
@Bean
public XsdSchema countriesSchema() {
return new SimpleXsdSchema(new ClassPathResource("countries.xsd"));
}
Una vez que la configuración del proyecto está completa, estamos listos para probarlo.
Podemos crear un archivo WAR y desplegarlo en un servidor de aplicaciones externo, pero utilizaremos Spring Boot, lo que es más rápido y sencillo. Para hacer la aplicación ejecutable, añadimos la siguiente clase:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Con esto, ejecutamos la aplicación:
mvn spring-boot:run
Para verificar que la aplicación está funcionando correctamente, podemos abrir el WSDL a través de la URL: http://localhost:8080/ws/countries.wsdl.
Para probar una solicitud, crearemos el siguiente archivo llamado request.xml:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:gs="http://www.baeldung.com/springsoap/gen">
<soapenv:Header/>
<soapenv:Body>
<gs:getCountryRequest>
<gs:name>Spain</gs:name>
</gs:getCountryRequest>
</soapenv:Body>
</soapenv:Envelope>
Usaremos herramientas externas como SoapUI o la extensión Wizdler de Google Chrome. Otra forma de enviar la solicitud es usando el siguiente comando en nuestra terminal:
curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws
La respuesta resultante podría no ser fácil de leer sin formato. Para ver el resultado de forma más clara, podemos copiarlo y pegarlo en nuestro IDE o utilizar xmllint:
curl [command-line-options] | xmllint --format -
La respuesta debería contener información sobre España:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2="http://www.baeldung.com/springsoap/gen">
<ns2:country>
<ns2:name>Spain</ns2:name>
<ns2:population>46704314</ns2:population>
<ns2:capital>Madrid</ns2:capital>
<ns2:currency>EUR</ns2:currency>
</ns2:country>
</ns2:getCountryResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
En este artículo, aprendimos cómo crear un servicio web SOAP utilizando Spring Boot. También demostramos cómo generar código Java a partir de un archivo XSD y configuramos los beans de Spring necesarios para procesar las solicitudes SOAP.
Implementar y probar un servicio web SOAP puede parecer complicado al principio, pero siguiendo estos pasos verás que es un proceso manejable. Este enfoque “contrato primero” ayuda a establecer una base clara para tu servicio desde el principio.
Si deseas profundizar más, considera explorar documentación adicional sobre Spring WS y SOAP, así como las mejores prácticas en el desarrollo de servicios web.