Un po’ come il dilemma shakespeariano, la scelta del tipo di servizio web attanaglia la vita di un architetto software: “REST o SOAP”? A discapito della letteratura, il dilemma sembra risolto a favore di REST, vuoi un po’ per moda, un po’ perché effettivamente è più facile da gestire e sfrutta appieno le tecnologie esistenti, senza aggiungere strati di XML su HTTP come fa SOAP. In effetti, SOAP è un protocollo sul protocollo, mentre REST sfrutta totalmente HTTP e i suoi verbi. In due post precedenti abbiamo già visto come sia facile creare e consumare servizi SOAP per Tomcat. Quando però passiamo da Tomcat ad un Application Server (AS) Java EE 6 compliant, abbiamo la possibilità di scrivere servizi sia REST che SOAP con qualche semplice annotazione, grazie alle specifiche JAX-WS e JAX-RS implementate nativamente da questi AS. Prendiamo JBoss 6 per esempio e vediamo effettivamente quanto è semplice realizzarli.

SOAP Service Producer

Quando si parla di SOAP, nel caso in cui si lavori in Tomcat viene in mente subito Axis. Con JBoss invece? La specifica che regolamenta i servizi web SOAP in Java EE è chiamata JAX-WS e in JBoss è implementata da un framework chiamato JBossWS. Rimandiamo alla guida JBoss AS 6.0 WebServices Guide per tutti gli approfondimenti. In questa occasione, vedremo semplicemente le annotazioni base, necessarie per creare velocemente un servizio web.

Servizi nel modulo Web

Senza fare troppi discorsi, partiamo subito con un esempio:

@WebService
public class MySOAPService {

     @WebMethod
     public String echo(String message) {
          return "Echo " + message;
     }
     
     @WebMethod
     public Output sort(Input input) {
          Arrays.sort(input.getVector());
          return new Output(input.getVector());
     }
}

Se avete letto il post sui servizi SOAP per Tomcat riconoscerete la classe se non per le annotazioni @WebService e @WebMethod. Che cosa abbiamo fatto quindi? Abbiamo creato una classe (POJO) in un progetto Web e poi l’abbiamo semplicemente annotata come servizio web (@WebService), dopodiché abbiamo definito i suoi metodi come fruibili da web (@WebMethod). Tutto qui? Ci siamo quasi: è necessario definire un alias per le chiamate web: come? Basta definire la classe nel web.xml come se fosse una Servlet:

<servlet>
  <servlet-name>MySOAPService</servlet-name>
  <servlet-class>it.cosenonjaviste.ws.MySOAPService</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>MySOAPService</servlet-name>
  <url-pattern>/MySOAPService</url-pattern>
</servlet-mapping>

Tornando alla classe, il metodo sort prende in ingresso e in uscita due oggetti: come fa JBoss a sapere come serializzarli? Ma con JAXB naturalmente! Che roba è? JAXB sta per Java Architecture for XML Binding: si tratta di una specifica che definisce una serie di annotazioni che permettono il marshalling/unmarshalling di XML in oggetti Java. Guardiamo per esempio come annotare la classe Input:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Input {

     @XmlElement
     private int[] vector;

     public Input() {
     }
     
     public Input(int... numbers) {
          this.vector = numbers;
     }
     
     public int[] getVector() {
          return vector;
     }

     public void setVector(int[] vector) {
          this.vector = vector;
     }
}

Con qualche annotazione, l’AS saprà come serializzare la classe in XML. Da notare l’annotazione @XmlAccessorType(XmlAccessType.FIELD): indica al marshaller di cercare le annotazioni @XmlElement sugli attributi invece che sui getter, come di default.

E’ facile intuire che possiamo tranquillamente annotare questa classe per esempio con annotazioni sia JAXB che JPA, senza che le une interferiscano con le altre. Se necessario quindi, la nostra entità potrà essere tranquillamente serializzata e inviata ad un client web con pochissimo sforzo… figo no?!

Se le classi sono state correttamente annotate e registrate nel web.xml, nel log del server, al deploy dovremmo trovare qualcosa di simile a:

id=MySOAPService
address=http://localhost:8080/JBossWebServiceProducerWeb/MySOAPService
implementor=it.cosenonjaviste.ws.MySOAPService
invoker=org.jboss.wsf.stack.cxf.InvokerJSE
serviceName={http://ws.cosenonjaviste.it/}MySOAPServiceService
portName={http://ws.cosenonjaviste.it/}MySOAPServicePort
wsdlLocation=null
mtomEnabled=false

A questo punto, per vedere se il servizio funziona, andiamo a chiedere al server il WSDL, generato dinamicamente a differenza di Axis:

http://localhost:8080/JBossWebServiceProducerWeb/MySOAPService?WSDL

Servizi nel modulo EJB

Nella piattaforma Enterpise è possibile anche esporre come servizi web gli EJB, ovvero classi che implementano la logica di business di una applicazione. Basta quindi usare l’annotazione @WebService a livello di classe e @WebMethod a livello di metodo proprio come nel caso precedente ed il gioco è fatto! A differenza del modulo Web, non importa configurare nessun file XML: al momento del deploy, in console appare qualcosa di simile:

id=MySOAPServiceEJB
address=http://localhost:8080/JBossWebServiceProducerEJB/MySOAPServiceEJB
implementor=it.cosenonjaviste.sessionbeans.ws.MySOAPServiceEJB
invoker=org.jboss.wsf.stack.cxf.InvokerEJB3
serviceName={http://ws.sessionbeans.cosenonjaviste.it/}MySOAPServiceEJBService
portName={http://ws.sessionbeans.cosenonjaviste.it/}MySOAPServiceEJBPort
wsdlLocation=null
mtomEnabled=false

Verifichiamo il WSDL:

http://localhost:8080/JBossWebServiceProducerEJB/MySOAPServiceEJB?WSDL

SOAP Service Consumer

Per quanto riguarda il lato consumer del servizio, torniamo a fare affidamento al generatore di codice di Eclipse, al quale diamo in pasto il WSDL ottenuto in precedenza. Ricordate il tutorial passo passo per generare i consumer su Tomcat? Basta fare una piccola modifica al passo 2, selezionando JBossWS come Web Service Runtime:

Selezionare il Web Service runtime: JBossWS

Alla fine, ci ritroveremo nel progetto tutti i file necessari ad invocare il servizio. Se diamo uno sguardo al codice generato, vedremo che anche nella parte client le annotazioni la fanno da padrona. A questo punto non rimane che testarlo!

SOAP, JBoss e JUnit

A differenza del codice generato con Axis, le nuove classi avranno bisogno delle funzionalità dell’Application Server per funzionare: essendo basate su annotazioni infatti, serve qualcuno capace di interpretarle. Per fortuna, per i test di unità, JBoss mette a disposizione una classe che ha proprio questo scopo, permettendo di creare test JUnit come il seguente:

@RunWith(JUnit4.class)
public class MySOAPServiceServiceTest extends JBossWSTest {

   private static MySOAPServiceService serviceService;
   
   @BeforeClass
   public  static void setUpTest() {
      serviceService = new MySOAPServiceService();
   }
   
   @Test
   public void testEcho() {
      MySOAPService soapService = serviceService.getMySOAPServicePort();
      String echo = soapService.echo("bau");
      
      assertEquals("Echo bau", echo);
   }
   
   @Test
   public void testSort() throws Exception {
      MySOAPService soapService = serviceService.getMySOAPServicePort();
      
      Input input = new Input();
      input.vector = Arrays.asList(4, 2, 6, 5, 1);
      
      Output output = soapService.sort(input);
      
      assertNotNull(output);
      assertNotNull(output.getSortedVector());
      assertFalse(output.getSortedVector().isEmpty());
      assertEquals(new Integer[] {1, 2, 4, 5, 6}, output.getSortedVector().toArray(new Integer[]{}));
   }
}

Creando una classe che estende JBossWSTest, il test verrà lanciato con JUnit 3 ed eseguito correttamente. Aggiungendo @RunWith(JUnit4.class) e le altre annotazioni, come nell’esempio, è possibile sfruttare invece JUnit 4!

Conclusioni

Fin qui abbiamo visto come creare con pochissimo sforzo un producer e un consumer di servizi web con Java EE 6. Nel prossimo post, cercheremo di fare la stessa cosa per i servizi REST.

83 Posts

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+