Spring WS – Sviluppare WebService in Java

Transcript

Spring WS – Sviluppare WebService in Java
Spring WS – Sviluppare WebService in Java
1.1 Introduzione
Nell’ambito della programmazione Java è particolarmente utile avere una certa familiarità con i WebService
e la tecnologia che li supporta. In particolare negli ultimi anni questa tecnologia si è diffusa notevolmente in
virtù della facilità di sviluppo e di condivisione di piattaforme.
Tuttavia è necessario chiarire cosa è un servizio Web: un web service è un’applicazione disponibile sul web
che si rende disponibile agli utenti attraverso una comoda interfaccia denominata WSDL. Questa interfaccia
è utile in quanto numerosi ambienti di programmazione permettono di leggerne il contenuto e di creare le
apposite classi per gestire le chiamate al servizio stesso.
1.2 Sviluppo Software
Sviluppare un WebService è particolarmente semplice se si sfruttano le funzionalità si SpringWS. In
particolare in questa guida tenterò di rispondere ad alcune delle domande + diffuse in merito alla creazione
di un web service. Inoltre verrà allegato un progetto d’esempio utile per esercitarsi e capire appieno le
potenzialità di Spring.
Sulla base di queste considerazioni consideriamo i seguenti punti:
1) Scaricare le librerie necessarie al funzionamento di SpringWS:
 aspectjweaver.jar
 commons-codec-1.3.jar
 commons-dbcp-1.2.2.jar
 commons-httpclient-3.1.jar
 commons-logging-1.1.1.jar
 commons-pool.jar
 ibatis-2.3.4.726.jar
 jdom-1.0.jar
 jaxen-1.1-beta-5.jar
 saaj-api.jar
 saaj-impl.jar
 spring-2.5.6.jar
 spring-core-2.5.6.jar
 spring-ws-1.5.7-all.jar
 wsdl4j-1.6.2.jar
che sono liberamente condivise sul web (docjar.com, jarfinder.com)
2) Definire gli oggetti che dovranno essere scambiati attraverso il servizio e realizzare lo schema
xsd corrispondente.
3) Importare il progetto in Eclipse/NetBeans quindi a partire dallo schema xsd creare le classi di
binding attraverso JAXB (in alternativa sul web è disponibile un tool da linea di comando)
4) Sviluppare le classi di Endpoint e Service
5) Definire il web.xml
6) Definire il file di configurazione di SpringWS
1.3 Definizione del Xsd Schema
La definizione di uno schema Xsd è piuttosto semplice perché riguarda sostanzialmente gli elementi che
devono essere scambiati e come questi vengano incapsulati nella chiamata e nella risposta. Supponiano che
si voglia inviare una banale stringa che debba poi essere stampata in output. La definizione dell’elemento è
la seguente:
-
<element name="SalutoRichiesta" type="string"/>
<element name="SalutoRisposta" type="string"/>
Tuttavia questo non basta!!! Per far funzionare il servizio è necessario incapsulare la stringa all’interno di
un oggetto che per consuetudine dovrà avere (per i due casi) la seguente nomenclatura:
-
ServiceRequest
ServiceResponse
L’xsd completo è il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.marcoamato.altervista.org/types"
xmlns:tns="http://www.marcoamato.altervista.org/types"
elementFormDefault="qualified">
<!-- REQUEST -->
<element name="ServiceRequest">
<complexType>
<all>
<element name="SalutoRichiesta" type="string"/>
</all>
</complexType>
</element>
<!-- RESPONSE -->
<element name="ServiceResponse">
<complexType>
<all>
<element name="SalutoRisposta" type="string"/>
</all>
</complexType>
</element>
</schema>
Notiamo in particolare la presenza del tag ComplexType che definisce un oggetto che contiene all’interno
un secondo oggetto di tipo stringa. Il targetNamespace ed il tns presenti nell’intestazione dello schema
devono inoltre essere modificati perché determineranno la posizione delle classi di binding nel progetto
(definiscono il package) mentre i nomi utilizzati per gli elementi saranno invece i nomi della classi di
binding.
Dopo aver completato l’editing dello schema xsd, possiamo passare all’operazione di binding delle classi
sfruttando l’opzione “Generate” presente in Eclipse e scegliendo come metodologia per il binding JAXB . Il
risultato è il seguente:
Come possiamo vedere è presente una classe di tipo ObjectFactory che contiene una serie di metodi per la
creazione di oggetti di tipo ServiceRequest e ServiceResponse. La letteratura informatica raccomanda in
generale di creare gli oggetti associati agli elementi del xsd attraverso l’utilizzo della classe ObjectFactory.
1.4 Classi Endpoint e Service
In questa sezione definiremo le classi necessarie all’implementazione del servizio vero e proprio. La classe
di Endpoint specifica appunto l’endpoint del servizio (dove questo è collocato) e come utilizzare il servizio
per effettuare la chiamata (quale metodo del servizio deve essere utilizzato). La classe di Service invece
implementa il servizio, nel senso che qui sono previste tutte quelle funzionalità aggiuntive che debbano
essere effettuate sull’oggetto che riceviamo.
Prima di effettuare la trattazione specifica delle classi, è doveroso fare una puntualizzazione: il servizio che
andremo a sviluppare permetterà lo scambio di banali messaggi di testo ma, per effettuare tali tipi di
operazioni, dovrà ricevere un oggetto di tipo JAXBElement<ServiceRequest> e rispondere con un oggetto di
tipo JAXElement<ServiceResponse>. È chiaro che questa è una complicazione soprattutto, lo vedremo in
seguito, in merito alla ricezione dell’oggetto che ci è stato inviato ma è necessaria al fine di fornire uno
strumento che si possa utilizzare in qualsiasi circostanza. Infatti inviando un oggetto piuttosto che un
insieme di stringhe, valori numerici, ecc…. è particolarmente + semplice da gestire e utilizzare lato back
end.
1.4.1 Classe Endpoint
La classe Endpoint viene creata come estensione di una classe astratta presente nel package di SpringWS e
denominata AbstractMarshallingPayloadEndpoint che prevede la funzionalità di deserializzazione
(unmarshalling) del payload della richiesta e la serializzazione (marshalling) della risposta. In particolare si
definisce un costruttore a due argomenti (il servizio ed il marshaller) che sarà utile al momento della
creazione del file di configurazione di SpringWS, e si sovrascrive un metodo contenuto appunto nella classe
astratta la cui firma è public Object invokeInternal(Object obj) throws Exception. Questo metodo verrà
utilizzato dalla classe di Endpoint per effettuare la chiamata effettiva al servizio effettuando appunto il
marshalling e l’unmarshalling. Il risultato è il seguente:
public Object invokeInternal(Object object) throws Exception
{
JAXBElement<ServiceRequest> request = null;
if(object instanceof javax.xml.bind.JAXBElement)
{
request = ((javax.xml.bind.JAXBElement<ServiceRequest>) object);
}
else
{
request = new JAXBElement<ServiceRequest>(new
QName("http://www.marcoamato.altervista.org/types","ServiceRequest"),
ServiceRequest.class, null, (org.altervista.marcoamato.types.ServiceRequest)object);
}
System.out.println(request);
try
{
JAXBElement<ServiceResponse> _return = myService.invioSaluto(request);
return _return;
}
catch (Exception ex)
{
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
Come possiamo vedere, abbiamo un ciclo if in cui verifichiamo che, la richiesta inviata sia tipo JAXBElement
o tipo ServiceRequest: nel primo caso l’oggetto cosi com’è viene utilizzato dal metodo mentre nel secondo
caso si crea l’oggetto JAXBElement sfruttando il costruttore in cui passiamo il QName (classe che definisce il
QualifiedName come specificato nel xsd, rappresentato dall’URI dell’oggetto – il package se vogliamo - e dal
localPart - nome dell’oggetti - ), la classe dell’oggetto e l’oggetto ricevuto dalla richiesta.
Si crea quindi la risposta sfruttando il metodo dell’oggetto service a cui passiamo la richiesta di tipo
JAXBElement che abbiamo appositamente creato.
1.4.2 Classe Service
La classe Service è molto + semplice rispetto alla classe di Endpont in quanto prevede un unico metodo a
cui possiamo dare un nome a nostra scelta ma ricordando che tale metodo deve ricevere un oggetto di tipo
JAXBElement<ServiceRequest> e deve restituire un oggetto di tipo JAXBElement<ServiceResponse>. Questo
è il listato del codice:
public JAXBElement<ServiceResponse> invioSaluto(JAXBElement<ServiceRequest> request)
{
ServiceRequest req = request.getValue();
ServiceResponse res = new
org.altervista.marcoamato.types.ObjectFactory().createServiceResponse();
System.out.println(req.getSalutoRichiesta());
res.setSalutoRisposta(req.getSalutoRichiesta());
JAXBElement<ServiceResponse> response = new JAXBElement<ServiceResponse>(new
QName("http://www.marcoamato.altervista.org/types","ServiceResponse"),
ServiceResponse.class, null, res);
return response;
}
In questo caso sostanzialmente abbiamo fatto l’operazione inversa all’Endpoint in quanto costruiamo un
oggetto JAXBElement<ServiceResponse> attraverso l’oggetto ServiceResponse di cui abbiamo settato la
risposta con il metodo “setSalutoRisposta” creato al momento del binding con JAXB.
1.5 Web.xml e SpringWS – servlet
Dopo aver dunque generato le classi necessarie per implementare il servizio, definiamo gli ultimi due
componenti ovvero il web.xml ed il file di configurazione di SpringWS. Infatti nell’ambito di un progetto
SpringWS sfruttiamo sostanzialmente le caratteristiche di un progetto Web Java in cui è presente il
web.xml: si configurerà questo file in modo che risponda ad un servlet di Spring mappata attraverso
ulteriore file di configurazione. Il descrittore è dunque il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/webapp_2_4.xsd">
<display-name>MyService</display-name>
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>
org.springframework.ws.transport.http.MessageDispatcherServlet
</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Il file è del tutto simile a quelli comunemente utilizzati in altre applicazioni web sebbene osserviamo la
presenza della servlet di Spring ovvero la classe MessageDispatcherServlet che permette l’invio e la
ricezione dei messaggi attraverso servlet. D’altra importante è il server-name che definirà non solo il nome
della servlet ma anche il nome del file di configurazione come: spring-ws-servlet.xml. Si aggiunge dunque al
nome della servlet il suffisso –servlet.
Il file di configurazione di SpringWS è il seguente:
01 <?xml version="1.0" encoding="UTF-8"?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04
xmlns:p="http://www.springframework.org/schema/p"
05
xmlns:context="http://www.springframework.org/schema/context"
06
xsi:schemaLocation="http://www.springframework.org/schema/beans
07
http://www.springframework.org/schema/beans/spring-beans.xsd
08
http://www.springframework.org/schema/context
09
http://www.springframework.org/schema/context/spring-context.xsd">
10
11
<!-- bean del servizio -->
12
<bean id="myService"
13
class="org.altervista.marcoamato.service.MyService" />
14
15
<!-- bean che contiene la lista delle classi per cui
16
é necessario effettuare SERIALIZZAZIONE E DESERIALIZZAZIONE -->
17
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
18
<property name="classesToBeBound">
19
<list>
20
<value>org.altervista.marcoamato.types.ServiceRequest</value>
21
<value>org.altervista.marcoamato.types.ServiceResponse</value>
22
</list>
23
</property>
24
</bean>
25
26
<!-- bean che gestisce l'endpoint tramite un costruttore
27
a cui passiamo il servizio come parametro -->
28
<bean id="myServiceEndpoint"
29
class="org.altervista.marcoamato.endpoint.MyServiceEndpoint">
30
<constructor-arg ref="myService" />
31
<constructor-arg ref="marshaller" />
32
</bean>
33
34
<!-35
<bean id="myServiceEndpointAdapter"
36
class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
37
<constructor-arg ref="marshaller" />
38
</bean> -->
39
40
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
41
<property name="mappings">
42
<props>
43
<prop key="{http://www.marcoamato.altervista.org/types}ServiceRequest">myServiceEndpoint
44
</prop>
45
</props>
46
</property>
47
</bean>
48
49
<bean id="endpointMapping" class="org.springframework.ws.soap.server.endpoint.mapping.SoapActionEndpointMapping">
50
<property name="mappings">
51
<props>
52
<prop key="http://www.marcoamato.altervista.org/types/ServiceRequest">myServiceEndpoint</prop>
53
</props>
54
</property>
55
</bean>
56
57
58
<bean id="exceptionResolver"
59
class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
60
<property name="defaultFault" value="SERVER"/>
61
<property name="exceptionMappings">
62
<value>
63
org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
64
</value>
65
</property>
66
</bean>
67
68
69
<!-- bean che identifica lo schema XSD -->
70
<bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
71
<property name="xsd" value="/WEB-INF/schema/Service.xsd" />
72
</bean>
73
74
<!-- bean che permette di creare automaticamente il servizio attraverso
75
l'xsd definito in precedenza -->
76
<bean id="contract"
77
class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
78
<property name="schema" ref="schema" />
79
<property name="portTypeName" value="Saluto" />
80
<property name="locationUri" value="/services" />
81
<property name="targetNamespace" value="http://localhost:9080/TestSpring/" />
82
</bean>
83
84 </beans>
Leggiamo il listato in modo che sia chiaro come sia stato editato:
-
-
Nella riga 12 abbiamo il bean del servizio definito dalla classe
Nella riga 17-24 definiamo il marshaller attraverso la classe Jaxb2Marsheller per cui è presente una
proprietà “classesToBeBound” esplicitando nel tag list le classi per cui dovrà essere effettuato il
binding
Nella riga 28-32 definiamo il bean dell’endpoint il costruttore prevede due argomenti: il servizio ed
il marshaller opportunamente definiti precedentemente
-
-
Nella riga 40-55 definiamo il mapping per l’endpoint utilizzando due classi come
PayloadRootQNameEndpointMapping e SoapActionEndpointMapping. All’interno di ciascuno di
questi bean è presente una proprietà che definisce l’oggetto che ci si aspetta di ricevere come
richiesta
Nella riga 58-66 definiamo l’ExceptionResolver così come presente nella guida di SpringWS
Nella riga 70 definiamo il bean dello schema xsd di riferimento
Nella riga 74-82 definiamo il bean che ci permetterà di pubblicare il wsdl. In questo bean sono
presenti alcune proprietà tra cui lo schema, il portTypeName che definisce il nome logico attribuito
alle operazioni definite in un wsdl, la location Uri che definisce parte della posizione del wsdl, ed il
targetNameSpace che definisce l’indirizzo a cui fare riferimento per visualizzare il wsdl.
Attraverso il targetNameSpace + location Uri + beanName.wsdl è possibile risalire all’indirizzo della
pubblicazione del wsdl e dunque per raggiungere il WebService appena creato.
Per provare il progetto basterà utilizzare SoapUi e creare un nuovo progetto allegando il wsdl.
Buon Lavoro a tutti!!!