Creare un WebService con Spring WS
Analizziamo la realizzazione di un semplice WebService, sfruttando le API che ci vengono messe a disposizione da Spring WS.
Spring Web Services ci permette di realizzare dei WebService utilizzando la metodologia contract-first, ovvero partendo dalla definizione del WSDL per poi realizzare il servizio che si nasconde dietro. Non mi dilungo sulle motivazioni che hanno spinto i creatori di Spring Web Services a scegliere questa metodologia e vi rimando alla documentazione ufficiale dove viene spiegato nel dettaglio.
A differenza della metodologia contract-last, dove il WSDL viene generato automaticamente a partire dal servizio che sviluppiamo, in questo caso il punto centrale nella definizione del WebService è il WSDL che viene esposto. A partire da questo WSDL poi noi agganciamo la nostra implementazione.
Definizione del servizio
Ipotizziamo di dover realizzare un classico servizio per la conversione delle valute (mi sono impegnato ma alla fine non ho trovato di meglio 😛 ). Quello che viene inviato al servizio come richiesta è un valore da convertire e il tipo di conversione. Dovendo partire dalla definizione del contratto, realizziamo un XSD dove mappiamo queste informazioni
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://com.javastaff.springws/conversione/ConversioneService/schema" xmlns:tns="http://com.javastaff.springws/conversione/ConversioneService/schema" elementFormDefault="qualified" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:Access="http://com.javastaff.springws/conversione/ConversioneService/schema" jaxb:version="2.0"> <element name="convertiRequest"> <complexType> <sequence> <element name="value" nillable="false"> <simpleType> <restriction base="decimal"> <totalDigits value="14"/> <fractionDigits value="2"/> </restriction> </simpleType> </element> <element name="conversionType" type="Access:ConversionType" /> </sequence> </complexType> </element> <element name="convertiResponse"> <complexType> <sequence> <element name="returnValue" nillable="false"> <simpleType> <restriction base="decimal"> <totalDigits value="14"/> <fractionDigits value="2"/> </restriction> </simpleType> </element> </sequence> </complexType> </element> <simpleType name="ConversionType"> <restriction base="string"> <enumeration value="EU"> <annotation> <appinfo> <jaxb:typesafeEnumMember name="EUR_TO_USD"/> </appinfo> </annotation> </enumeration> <enumeration value="UE"> <annotation> <appinfo> <jaxb:typesafeEnumMember name="USD_TO_EUR"/> </appinfo> </annotation> </enumeration> </restriction> </simpleType> </schema>
In questo XSD abbiamo definito che la request ha due elementi, il valore da convertire e la tipologia di conversione, definita all’interno dello schema come un simpleType.
Generazione del codice
Passiamo ora alla generazione delle classi Java che vengo descritte dal nostro servizio. Per fare questo possiamo utilizzare il plugin Maven per JAXB2, che a partire dal file XSD genera le classi con le relative annotation JAXB. Qui di seguito viene riportato il pom.xml del nostro progetto d’esempio
<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.javastaff</groupId> <artifactId>springws</artifactId> <packaging>war</packaging> <version>1.0</version> <name>SpringWS</name> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>3.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-core</artifactId> <version>2.1.3.RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jvnet.jaxb2.maven2</groupId> <artifactId>maven-jaxb2-plugin</artifactId> <version>0.8.0</version> <configuration> <removeOldOutput>false</removeOldOutput> <generatePackage> com.javastaff.springws </generatePackage> <generateDirectory> ${basedir}/src/main/java/ </generateDirectory> </configuration> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> </plugin> </plugins> <finalName>springws</finalName> </build> </project>
Sono state definite le dipendenze per Spring WS e nel build del nostro progetto abbiamo aggiunto un plugin che ci permette di generare le classi. Facendo un build del nostro progetto troveremo le classi generate da JAXB, come quella che gestisce la request che viene riportata qui di seguito
package com.javastaff.springws; import java.math.BigDecimal; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "", propOrder = { "value", "conversionType" }) @XmlRootElement(name = "convertiRequest") public class ConvertiRequest { @XmlElement(required = true) protected BigDecimal value; @XmlElement(required = true) protected ConversionType conversionType; /** * Gets the value of the value property. * * @return * possible object is * {@link BigDecimal } * */ public BigDecimal getValue() { return value; } /** * Sets the value of the value property. * * @param value * allowed object is * {@link BigDecimal } * */ public void setValue(BigDecimal value) { this.value = value; } /** * Gets the value of the conversionType property. * * @return * possible object is * {@link ConversionType } * */ public ConversionType getConversionType() { return conversionType; } /** * Sets the value of the conversionType property. * * @param value * allowed object is * {@link ConversionType } * */ public void setConversionType(ConversionType value) { this.conversionType = value; } }
Nella definizione del nostro servizio abbiamo riportato anche un simpleType, ConversionType, che utilizza il namespace di JAXB per indicare la generazione di un Enum. Proprio per questo motivo tra le classi generate avremo l’Enum corrispondente
package com.javastaff.springws; import javax.xml.bind.annotation.XmlEnum; import javax.xml.bind.annotation.XmlEnumValue; import javax.xml.bind.annotation.XmlType; @XmlType(name = "ConversionType") @XmlEnum public enum ConversionType { @XmlEnumValue("EU") EUR_TO_USD("EU"), @XmlEnumValue("UE") USD_TO_EUR("UE"); private final String value; ConversionType(String v) { value = v; } public String value() { return value; } public static ConversionType fromValue(String v) { for (ConversionType c: ConversionType.values()) { if (c.value.equals(v)) { return c; } } throw new IllegalArgumentException(v); } }
Configurazione del web.xml
Ora che abbiamo definito il servizio e generate le classi per gestire le richieste, possiamo passare a Spring WS. La prima cosa che dobbiamo inserire nella nostra applicazione è il caricamento del contesto Spring dal web.xml. Se avete avuto modo di lavorare con Spring MVC avrete già avuto modo di fare una cosa analoga, definendo la DispatcherServlet che è il Front Controller attraverso cui funziona l’MVC di Spring.
Spring WS funziona in maniera analoga e quindi dovremo definire la classe MessageDispatcherServlet come Front Controller della nostra applicazione
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>SpringWS</display-name> <servlet> <servlet-name>spring-ws</servlet-name> <servlet-class> org.springframework.ws.transport.http.MessageDispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-ws-context.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/conversione/*</url-pattern> </servlet-mapping> </web-app>
In questo modo tutte le chiamate che la nostra applicazione web riceverà sul path /conversione/ saranno gestite da Spring WS. Come potete vedere viene passato un parametro alla Servlet, contextConfigLocation,che è la definizione del contesto di Spring WS dove dovremo appunto andare a riportare la configurazione che vorremo utilizzare per il nostro servizio.
Contesto di Spring WS
Dobbiamo definire ora il nostro WebService, partendo dal presupposto che il punto di partenza deve essere il servizio che abbiamo definito nel file XSD. Nella parte iniziale del file di configurazione facciamo le classiche definizioni per i namespace di 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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.javastaff.springws" />
Oltre alla definizione dei namespace abbiamo riportato anche il tag context:component-scan, che si occuperà di ricercare i componenti definiti tramite Annotation.
La prima cosa che viene configurata sarà un’istanza di DefaultWsdl11Definition, che è una classe che si occupa di generare il WSDL del nostro servizio
<bean id="ConversioneService" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition"> <property name="schema" ref="conversioneServiceSchema" /> <property name="portTypeName" value="ConversioneService" /> <property name="locationUri" value="http://localhost:8080/SpringWS/conversione/ConversioneService/" /> <property name="targetNamespace" value="http://com.javastaff.springws/conversione/ConversioneService/schema" /> </bean> <bean id="conversioneServiceSchema" class="org.springframework.xml.xsd.SimpleXsdSchema"> <property name="xsd" value="/WEB-INF/conversione.xsd" /> </bean>
Così facendo abbiamo definito le seguenti informazioni
- schema: è un implementazione di org.springframework.xml.xsd.SimpleXsdSchema, che prende come input il file XSD definito in precedenza
- portTypeName: il nome del nostro servizio
- locationUri: dove sarà possibile contattare il nostro servizio
- targetNamespace: il namespace utilizzato
Ora dobbiamo dire a Spring come deve effettuare il marshal delle classi utilizzate dal nostro servizio. In questo caso utilizziamo Jaxb2Marshaller, che si basa sulle annotation JAXB2 presenti nelle nostri classi.
<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter"> <constructor-arg ref="marshaller" /> </bean> <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="contextPath" value="com.javastaff.springws" /> </bean>
Come ultima parte del contesto Spring, inseriamo nella configurazione un PayloadLoggingInterceptor, Interceptor che ci permette di loggare le informazioni che transitano sul nostro servizio
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"> <property name="interceptors"> <list> <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor" /> </list> </property> </bean>
Se avete già sviluppato dei WebService utilizzando JAX-WS, troverete delle analogie tra la definizione di questi Interceptor e gli Handler. In entrambi i casi abbiamo a che fare con una sorta di filtri che mettiamo nel processamento della richiesta.
Implementazione del servizio
Il servizio che dobbiamo implementare è abbastanza semplice e l’interfaccia può essere quella che viene riportata nel seguente codice
package com.javastaff.springws; import java.math.BigDecimal; public interface ConversioneService { public BigDecimal converti(BigDecimal value, ConversionType conversionType); }
La classe che implementa questa interfaccia utilizzerà l’annotation Service di Spring per essere utilizzata direttamente nel nostro WebService
package com.javastaff.springws; import java.math.BigDecimal; import org.springframework.stereotype.Service; @Service(value="conversionService") public class ConversioneServiceImpl implements ConversioneService{ private final double EU_rate=1.3043; public BigDecimal converti(BigDecimal value, ConversionType conversionType) { BigDecimal returnValue=null; switch (conversionType) { case EUR_TO_USD: returnValue=value.multiply(BigDecimal.valueOf(EU_rate)); break; case USD_TO_EUR: returnValue=value.divide(BigDecimal.valueOf(EU_rate)); break; default: break; } return returnValue; } }
Questo è il classico servizio Spring, ora invece dobbiamo implementare l’Endpoint che verrà richiamata da Spring WS per gestire il WebService che abbiamo definito.
package com.javastaff.springws; import java.math.BigDecimal; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; @Endpoint public class ConversioneEndpoint { @Autowired @Qualifier(value = "conversionService") ConversioneService conversioneService; private final ObjectFactory JAXB_OBJECT_FACTORY = new ObjectFactory(); private final static String NAMESPACE= "http://com.javastaff.springws/conversione/ConversioneService/schema"; @PayloadRoot(localPart = "convertiRequest", namespace = NAMESPACE) public JAXBElement<ConvertiResponse> converti( ConvertiRequest conversioneRequest) { ConvertiResponse response = JAXB_OBJECT_FACTORY.createConvertiResponse(); BigDecimal returnValue = conversioneService.converti( conversioneRequest.getValue(), conversioneRequest.getConversionType()); response.setReturnValue(returnValue); JAXBElement<ConvertiResponse> jaxbElementResponse; QName qname = new QName(NAMESPACE, "convertiResponse"); jaxbElementResponse = new JAXBElement<ConvertiResponse> (qname, ConvertiResponse.class, response); return jaxbElementResponse; } }
Con l’annotation @Endpoint comunichiamo a Spring WS che questa classe è l’endpoint per il nostro WebService. Poi con @PayloadRoot definiamo nello specifico la richiesta che viene gestita. Ora non ci resta che impacchettare il tutto e metterlo sul nostro server
Prova del servizio
Una volta effettuato il deploy del WAR, possiamo visualizzare il WSDL del servizio inserendo il seguente URL nel nostro browser: http://localhost:8080/SpringWS/conversione/ConversioneService.wsdl . Per fare un test al volo, senza dover creare un client, possiamo utilizzare un tool come soapUI dove andiamo a specificare l’URL del nostro WSDL
e in automatico abbiamo una richiesta di test da effettuare, dove dobbiamo semplicemente inserire i valori e vedere il risultato del nostro WebService

Looking for a right “about me”…
Commenti recenti