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