8. Versione 4 – client/server in un’architettura di servizi web
In questa nuova versione, l’applicazione [Pam] verrà eseguita in modalità client/server in un’architettura di servizi web. Torniamo all’architettura dell’applicazione precedente:
![]() |
Come illustrato sopra, un livello di comunicazione [C, RMI, S] consentiva una comunicazione trasparente tra il client [ui] e il livello remoto [metier]. Utilizzeremo un’architettura analoga, in cui il livello di comunicazione [C, RMI, S] sarà sostituito da un livello [C, HTTP / SOAP, S]:
![]() |
Il protocollo HTTP / SOAP presenta il vantaggio, rispetto al precedente protocollo RMI / EJB, di essere multipiattaforma. In questo modo, il servizio web può essere scritto in Java e distribuito sul server Glassfish, mentre il client potrebbe essere un client .NET o PHP.
Svilupperemo questa architettura secondo tre diverse modalità:
- il servizio web sarà gestito da EJB [Metier]
- il servizio web sarà gestito da un’applicazione web che utilizza EJB e [Metier]
- il servizio web sarà gestito da un'applicazione web che utilizza Spring
Un servizio web può essere implementato in vari modi all'interno di un server Java EE:
- tramite una classe annotata con @WebService che viene eseguita in un contenitore web
![]() |
- tramite un EJB annotato con @WebService che viene eseguito in un contenitore EJB
![]() |
Iniziamo con quest’ultima architettura.
8.1. Servizio web implementato da un EJB
8.1.1. La parte server
8.1.1.1. Il progetto NetBeans
Iniziamo creando un nuovo progetto Maven, copia del progetto EJB [mv-pam-ejb-metier-dao-jpa-eclipselink]:
![]() |
Con la seguente struttura:
![]() |
il livello [metier] sarà il servizio web interpellato dal livello [ui]. Questa classe non deve implementare alcuna interfaccia. Sono le annotazioni a trasformare un POJO (Plain Ordinary Java Object) in un servizio web. La classe [Metier], che implementa il livello [metier] sopra indicato, viene trasformata nel modo seguente:
package metier;
...
@WebService
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal,IMetierRemote {
// riferimenti ai livelli [DAO]
@EJB
private ICotisationDaoLocal cotisationDao = null;
@EJB
private IEmployeDaoLocal employeDao=null;
@EJB
private IIndemniteDaoLocal indemniteDao=null;
// recupero del foglio paga
@WebMethod
public FeuilleSalaire calculerFeuilleSalaire(String SS,
...
}
// elenco dei dipendenti
@WebMethod
public List<Employe> findAllEmployes() {
...
}
// importante - nessun getter e setter per i EJB
}
- alla riga 4, l’annotazione @WebService trasforma la classe [Metier] in un servizio web. Un servizio web espone dei metodi ai propri clienti. Questi devono essere annotati con l’attributo @WebMethod.
- righe 19 e 25: i due metodi della classe [Metier] diventano metodi del servizio web.
- riga 29: è importante che i getter e i setter vengano rimossi, altrimenti saranno esposti nel servizio web e ciò causerà errori di sicurezza.
L'aggiunta di queste annotazioni viene rilevata da NetBeans, che modifica quindi la natura del progetto:
![]() |
In [1], nel progetto è apparsa una struttura ad albero denominata [Web Services]. In essa si trovano il servizio web Metier e i suoi due metodi. L'applicazione server può essere distribuita in [2]. Il server MySQL deve essere avviato e il suo database [dbpam_eclipselink] deve esistere ed essere popolato. Potrebbe essere necessario, in precedenza, rimuovere [3] e EJB dal progetto client/server EJB esaminato in precedenza per evitare conflitti di nomi. Infatti, il nostro nuovo progetto porta con sé gli stessi EJB di quelli del progetto precedente.
![]() |
In [1], vediamo la nostra applicazione serveur distribuita sul server Glassfish. Una volta distribuito il servizio web, è possibile testarlo:
![]() |
- in [1], nel progetto corrente, testiamo il servizio web [Metier]
- il servizio web è accessibile tramite diversi URL. Il URL [2] consente di testare il servizio web
- in [3], un collegamento al file XML che definisce il servizio web. I clienti del servizio web devono conoscere l'URL di questo file. È a partire da esso che viene generato il livello client (stub) del servizio web.
- in [4,5], un modulo che consente di testare i metodi esposti dal servizio web. Questi sono presentati con i relativi parametri che l’utente può definire.
Ad esempio, proviamo il metodo [findAllEmployes] che non richiede alcun parametro:
![]() |
Nell’esempio sopra riportato, testiamo il metodo. Riceviamo quindi la risposta riportata di seguito (vista parziale). Vi ritroviamo effettivamente i due dipendenti con le relative indennità. Il lettore è invitato a testare allo stesso modo il metodo [4], passando i tre parametri richiesti.

8.1.2. La parte client
![]() |
8.1.2.1. Il progetto NetBeans del client console
Ora creiamo un progetto Java di tipo [Java Application] per la parte client dell’applicazione. Non è stato possibile (giugno 2012) creare un progetto Maven per questo client. Si verifica un errore, che sembra essere noto in rete ma rimane irrisolto.
![]() | ![]() |
Una volta creato il progetto, specifichiamo che sarà un client del servizio web che abbiamo appena distribuito sul server Glassfish:
![]() |
- in [2], selezioniamo il nuovo progetto e attiviamo il pulsante [New File]
- in [3], specifichiamo che vogliamo creare un client del servizio web
![]() |
- con [4], indicheremo il progetto NetBeans del servizio web
- nella finestra [5] sono elencati tutti i progetti con un ramo [Web Services], in questo caso solo il progetto [mv-pam-ws-metier-dao-eclipselink].
- Un progetto può distribuire più servizi web. In [6], si seleziona il servizio web a cui ci si vuole connettere.
![]() |
- In [7] viene visualizzata la definizione del servizio web URL. Questa URL viene utilizzata dagli strumenti software che generano il livello client che si interfaccerà con il servizio web.
![]() |
- Il livello client [C] [1] che verrà generato è costituito da un insieme di classi Java che saranno inserite in un unico pacchetto. Il nome di quest’ultimo è fissato in [8].
- Una volta completata la procedura guidata per la creazione del client del servizio web tramite il pulsante [Finish], viene creato il livello [C] sopra indicato.
Ciò si riflette in una serie di modifiche al progetto:
- Nel [10] sopra riportato, compare una struttura ad albero [Generated Sources] che contiene le classi del livello [C] che consentono al client [3] di comunicare con il servizio web. Questo livello consente al client [3] di comunicare con il livello [metier] e [4] come se fosse locale e non remoto.
- In [11] compare una struttura ad albero [Web Service References] che elenca i servizi web per i quali è stato generato un livello client.
Si noti che nel livello [C] [10] generato, troviamo classi che sono state implementate sul lato server: Indemnite, Cotisation, Employe, FeuilleSalaire, ElementsSalaire, Metier. Metier è il servizio web e le altre classi sono quelle necessarie a tale servizio. Si potrebbe essere curiosi di consultare il loro codice. Si noterà che la definizione delle classi che, una volta istanziate, rappresentano gli oggetti gestiti dal servizio, consiste nella definizione dei campi della classe e dei relativi accessori, nonché nell’aggiunta di annotazioni che consentono la serializzazione della classe nel flusso XML. La classe Metier è diventata un’interfaccia contenente i due metodi che sono stati annotati con @WebMethod. Ciascuno di questi metodi dà origine a due classi, ad esempio [CalculerFeuilleSalaire.java] e [CalculerFeuilleSalaireResponse.java], dove una incapsula la chiamata al metodo e l’altra il suo risultato. Infine, la classe MetierService è quella che consente al client di disporre di un riferimento al servizio web Metier remoto:
Il metodo getMetierPort alla riga 2 consente di ottenere un riferimento al servizio web Metier remoto.
8.1.2.2. Il client console del servizio web Metier
Non ci resta che scrivere il client del servizio web Metier. Copiamo la classe [MainRemote] dal progetto [mv-pam-client-metier-dao-jpa-eclipselink], che era un client di un server EJB, nel nuovo progetto.
![]() |
- in [1], la classe del client del servizio web. La classe [MainRemote] presenta degli errori. Per correggerli, si inizierà rimuovendo tutte le istruzioni [import] esistenti nella classe e le si rigenererà tramite l’opzione [Fix Imports]. Infatti, alcune delle classi utilizzate dalla classe [MainRemote] fanno ora parte del pacchetto [client] generato.
- In [3], il frammento di codice in cui viene istanziato il livello [metier] è [3]. L'istanza viene creata con il codice JNDI per ottenere un riferimento a un EJB remoto.
Modifichiamo il codice come segue:
- il codice JNDI viene rimosso
- poiché la classe [PamException] non esiste sul lato client, eliminiamo il catch associato per mantenere solo il catch sulla classe madre [Exception].
![]() |
- in [4], ci resta da ottenere un riferimento al servizio web remoto [Metier] per poter chiamare il suo metodo [calculerFeuilleSalaire].
- In [5], con il mouse trasciniamo (drag) il metodo [calculerFeuilleSalaire] dal servizio web [Metier] per rilasciarlo (drop) in [4]. Viene generato il codice [6]. Questo codice generico può poi essere adattato dallo sviluppatore.
![]() |
- alla riga 112, si vede che [calculerFeuilleSalaire] è un metodo della classe [client.Metier] (riga 111). Ora che sappiamo come ottenere il livello [metier], il codice precedente può essere riscritto nel modo seguente:
La riga 7 recupera un riferimento al servizio web Metier. Fatto ciò, il codice della classe non cambia, se non per il fatto che alla riga 10 non viene gestita l’eccezione di tipo [Exception], bensì quella di tipo più generale Throwable, la classe padre della classe Exception. In caso di eccezione, visualizziamo tutte le cause annidate fino alla causa originaria.
Siamo pronti per i test:
- assicurarsi che SGBD MySQL5 sia avviato, che il database dbpam_eclipselink sia creato e inizializzato
- assicurarsi che il servizio web sia distribuito sul server Glassfish
- compilare il client (Clean and Build)
- configurare l’esecuzione del client
![]() |
- Eseguire il client
I risultati nella console sono i seguenti:
Con la seguente configurazione:

si ottengono i seguenti risultati:
Si noti che, mentre il servizio web [Metier] invia un'eccezione di tipo [PamException], l'eccezione ricevuta dal client è di tipo [SOAPFaultException]. Anche nella catena delle eccezioni non compare il tipo [PamException].
8.1.3. Il client Swing del servizio web Metier
Lavoro da svolgere: trasferire il client Swing del progetto [mv-pam-client-ejb-metier-dao-jpa-eclipselink] nel nuovo progetto in modo che anch'esso diventi un client del servizio web distribuito sul server Glassfish.
8.2. Servizio web implementato da un'applicazione web
Ci collochiamo ora nel contesto della seguente architettura:
![]() |
Il servizio web è gestito da un'applicazione web in esecuzione all'interno del container web del server Glassfish. Questo servizio web si baserà su EJB [Metier], a sua volta distribuito nel container EJB3.
8.2.1. La parte server
Creiamo un’applicazione web:
![]() |
- in [1], creiamo un nuovo progetto
- in [2]; questo progetto è di tipo [Web Application]
- in [3], gli diamo il nome [mv-pam-ws-ejb-metier-dao-eclipselink]
![]() |
- in [4], scegliamo la versione Java EE 6
- in [6], il progetto creato
Nello schema sottostante, l’applicazione web creata verrà eseguita nel contenitore web. Utilizzerà il EJB [Metier], che a sua volta verrà distribuito nel contenitore EJB del server.
![]() |
Affinché l’applicazione web creata abbia accesso alle classi associate a EJB e [Metier], aggiungiamo alle librerie dell’applicazione web [mv-pam-ws-ejb-metier-dao-eclipselink], la dipendenza dal server EJB [mv-pam-ejb-metier-dao-eclipselink] già esaminata.
![]() |
- in [1], si aggiunge un progetto alle dipendenze del progetto web,
- in [2], si seleziona il progetto [mv-pam-ejb-metier-dao-eclipselink],
- in [3], il tipo di dipendenza è ejb,
- in [4], l'ambito della dipendenza è provided, ovvero sarà fornita dall'ambiente di esecuzione,
- in [5], la dipendenza è stata aggiunta.
Per creare lo stesso servizio web di prima, dobbiamo:
- creare una classe contrassegnata con @Webservice
- con due metodi calculerFeuilleSalaire e findAllEmployes contrassegnati con @WebMethod
Creiamo una classe [PamWsEjbMetier] in un pacchetto [pam.ws]:
![]() |
![]() |
La classe [PamWsEjbMetier] è la seguente:
- righe 7-10: la classe importa le classi del modulo EJB e [pam-serveurws-metier-dao-jpa-eclipselink], il cui progetto Maven è stato aggiunto alle dipendenze del progetto.
- riga 12: la classe è un servizio web
- riga 13: implementa l’interfaccia IMetier definita nel modulo EJB
- righe 18-19: il metodo calculerFeuilleSalaire è esposto come metodo del servizio web
- righe 23-24: il metodo findAllEmployes è esposto come metodo del servizio web
- righe 15-16: l'interfaccia locale di EJB [Metier] viene inserita nel campo della riga 16. Utilizziamo l'interfaccia locale poiché l'applicazione web e il modulo EJB vengono eseguiti nello stesso JVM.
- righe 20 e 25: i metodi calculerFeuilleSalaire e findAllEmployes delegano la loro elaborazione ai metodi omonimi di EJB [Metier]. La classe serve quindi solo a esporre ai client remoti i metodi di EJB e [Metier] come metodi di un servizio web.
In NetBeans, l’applicazione web viene riconosciuta come espositrice di un servizio web:
![]() |
Per distribuire il servizio web sul server GlassFish, è necessario distribuire sia:
- il modulo web nel contenitore web del server
- il modulo EJB nel contenitore EJB del server
A tal fine, dobbiamo creare un'applicazione di tipo [Enterprise Application] che distribuisca entrambi i moduli contemporaneamente. Per farlo, è necessario che entrambi i progetti siano caricati in NetBeans [2].
Una volta fatto ciò, creiamo un nuovo progetto [3].
![]() |
- in [4], scegliamo un progetto di tipo [Enterprise Application].
- in [5], assegniamo un nome al progetto
![]() |
- in [6], configuriamo il progetto. La versione di Java EE sarà Java EE 6. È possibile creare un progetto aziendale con due moduli: un modulo EJB e un modulo Web. In questo caso, il progetto aziendale incapsulerà il modulo Web e il modulo EJB già creati e caricati in NetBeans. Pertanto non richiediamo la creazione di nuovi moduli.
- In [7], il progetto aziendale [mv-pam-webapp-ear] così creato. Contemporaneamente è stato creato un altro progetto Maven, [mv-pam-webapp]. Di questo non ci occuperemo.
- In [8], aggiungiamo le dipendenze al progetto aziendale
![]() |
- in [9], aggiungiamo il progetto web di tipo WAR,
- in [10], si aggiunge il progetto EJB di tipo EJB,
![]() |
- in [11], il progetto aziendale con le sue due dipendenze.
Compiliamo il progetto aziendale con un «Clean and Build». Siamo quasi pronti a distribuirlo sul server Glassfish. In precedenza potrebbe essere necessario scaricare le applicazioni già caricate sul server per evitare eventuali conflitti di nomi tra EJB e [11]:
![]() |
Il server MySQL deve essere avviato e il database [dbpam_eclipselink] deve essere disponibile e popolato. Una volta fatto ciò, è possibile distribuire l’applicazione aziendale [12]. In [13] è possibile verificare che sia stata correttamente distribuita sul server Glassfish.
Possiamo testare il servizio web appena implementato:
![]() |
- in [1], chiediamo di testare il servizio web [PamWsEjbMetier]
- in [2], la pagina di test. Lasciamo al lettore il compito di eseguire i test.
8.2.2. La parte client
Compito da svolgere: seguendo la procedura descritta al paragrafo 8.1.2.1, realizzare un client da console per il servizio web precedente.
8.3. Servizio web implementato con Spring e Tomcat
Ci collochiamo ora nel contesto della seguente architettura:
![]() |
Il servizio web è gestito da un'applicazione web eseguita all'interno del container web del server Tomcat. L'architettura dell'applicazione sarà la seguente:
![]() |
Ci baseremo sul progetto [mv-pam-spring-hibernate] realizzato nel paragrafo 5.11:
![]() |
8.3.1. La parte server
Creiamo un’applicazione web Maven denominata [mv-pam-ws-spring-tomcat] [1]:
![]() |
Modifichiamo il file [pom.xml] per includervi le seguenti dipendenze [2]:
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mv-pam-spring-hibernate</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Apache CXF dipendenze -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.2.12</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.2.12</version>
</dependency>
</dependencies>
- righe 3-7: la dipendenza dal progetto [spring-pam-jpa-hibernate],
- righe 8-17: le dipendenze dal framework Apache CXF e [http://cxf.apache.org/]. Questo framework facilita la creazione di servizi web.
Questo file [pom.xml] comporta numerose dipendenze [2].
Torniamo all’architettura dell’applicazione:
![]() |
Le chiamate al servizio web che realizzeremo sono gestite da un servlet del framework CXF. Ciò si traduce nel file [WEB-INF / web.xml] nel modo seguente:
<?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>mv-pam-ws-spring-tomcat</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Configurazione di CXF -->
<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
- Il framework CXF dipende da Spring. Righe 4-6: viene dichiarato un listener. La classe corrispondente verrà caricata contemporaneamente all’applicazione web. Essa utilizzerà il file di configurazione di Spring [WEB-INF / applicationContext.xml]:
![]() |
- righe 8-12: il servlet CXF che gestirà le chiamate al servizio web che creeremo,
- righe 13-16: le richieste URL gestite dal servlet CXF saranno del tipo /ws/*. Le altre non saranno gestite da CXF.
Per definire il servizio web, definiamo un'interfaccia e la sua implementazione:
![]() |
L’interfaccia [IWsMetier] sarà la seguente:
package pam.ws;
import javax.jws.WebService;
import metier.IMetier;
@WebService
public interface IWsMetier extends IMetier{
}
- riga 7: l'interfaccia [IWsMetier] deriva dall'interfaccia [IMetier] del livello [métier] del progetto [mv-pam-spring-hibernate],
- riga 6: l'interfaccia [IWsMetier] è quella di un servizio web.
La classe di implementazione di questa interfaccia è la seguente:
package pam.ws;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebService;
import jpa.Employe;
import metier.FeuilleSalaire;
import metier.IMetier;
@WebService
public class PamWsMetier implements IWsMetier {
// livello business
private IMetier metier;
// costruttore
public PamWsMetier(){
}
@WebMethod
public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillees, int nbJoursTravailles) {
return metier.calculerFeuilleSalaire(SS, nbHeuresTravaillees, nbJoursTravailles);
}
@WebMethod
public List<Employe> findAllEmployes() {
return metier.findAllEmployes();
}
// getter e setter
public void setMetier(IMetier metier) {
this.metier = metier;
}
}
- riga 11: la classe [PamWsMetier] implementa l'interfaccia definita in precedenza,
- riga 10: definisce la classe come un servizio web,
- riga 14: il livello [métier] verrà iniettato da Spring,
- righe 21, 26: l’annotazione @WebMethod rende un metodo un metodo esposto dal servizio web,
- righe 23, 28: i metodi sono implementati utilizzando il livello [métier].
Resta da definire il contenuto del file di configurazione di Spring [applicationContext.xml]:
![]() |
Il suo contenuto è il seguente:
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<!-- Apache CXF -->
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<!-- livelli inferiori -->
<import resource="classpath:spring-config-metier-dao.xml" />
<!-- servizio web -->
<bean id="wsMetier" class="pam.ws.PamWsMetier">
<property name="metier" ref="metier"/>
</bean>
<jaxws:endpoint id="wsmetier"
implementor="#wsMetier"
address="/metier">
</jaxws:endpoint>
</beans>
- righe 13-15: si importano i file di configurazione Apache CXF. Questi vengono cercati nel Classpath del progetto (attributo classpath:),
- righe 4, 9, 10: vengono dichiarati spazi dei nomi specifici per Apache CXF,
- riga 18: si importa il file di configurazione Spring del progetto [mv-pam-spring-hibernate],
- righe 21-23: definiscono il bean del servizio web con la sua dipendenza dal livello [métier] (riga 22),
- righe 24-27: definiscono il servizio web stesso,
- riga 25: il bean Spring che implementa il servizio web è quello definito alla riga 21;
- riga 26: definisce il percorso URL in cui sarà disponibile il servizio web, in questo caso /metier. In combinazione con il formato che devono avere le URL elaborate da Apache CXF (cfr. file web.xml), questa URL diventa /ws/metier.
Il nostro progetto è pronto per essere eseguito. Lo eseguiamo (Run) e richiediamo l'URL [http://localhost:8080/mv-pam-ws-spring-tomcat/ws] in un browser:

La pagina elenca tutti i servizi web distribuiti. In questo caso, ce n’è solo uno. Seguiamo il link WSDL:
![]() |
Il testo visualizzato [1] è quello di un file XML che definisce le funzionalità del servizio web, come richiamarlo e quali risposte invia. Da notare il URL [2] di questo file WSDL. Tutti i client del servizio web devono conoscerlo.
8.3.2. La parte client
Compito da svolgere: seguendo la procedura descritta nel paragrafo 8.1.2.1, realizzare un client da console per il servizio web precedente.
Nota: per indicare l’URL del file WSDL del servizio web, si procederà come segue:
![]() |
Si inserirà in [3] il valore URL precedentemente annotato in [2].












































