Spring-Boot: Comment consommer un web-service Soap? Écrire un client web service par la pratique en 15 minutes

 

Nous allons présenter un guide pratique pour écrire un client java qui consomme un web service soap.

L'idée ici est de reprendre le guide de spring.io qui hélas s'appuie sur une url wsdl ne fonctionnant plus et qui rend le guide difficile à suivre.

Je propose ici d'utiliser l'url wsdl suivante: http://www.dneonline.com/calculator.asmx?wsdl

J'indique aussi la bonne version du plugin pour corriger une erreur de ce type:

Execution default of goal org.jvnet.jaxb2.maven2:maven-jaxb2-plugin:0.12.3:generate failed: A required class..

A noter ici que pour ce web service le targetNamespace="http://tempuri.org/".

Ce point est important pour la suite.

Vous pouvez lire la description de ce web-service ici.

C'est un ws basique de test offrant un calculateur (opérations mathématiques simples sur des entiers).

La seule opération qui nous intéresse ici est (Add) l'addition de deux entiers.

Et pour être exhaustif, nous écrivons un test JUnit, puis un ws Rest qui consommeront notre ws soap.

Rappelons notre objectif: Générer un ws client consommant le web service SOAP sans recourir à aucun framework supplémentaire.

Passons à la pratique avec Spring Boot.

 

Voici les étapes en 15 minutes (4C obligatoires et 3E optionnelles):

  • Step 1. Créer le projet depuis la page http://start.spring.io en choisissant les trois briques: Web, WS, Lombok,
  • Step 2. Configurer le plugin ws dans le pom (à partir de wsdl, on génère les classes java [domain object based on wsdl] utiles. Noter le nom du package déclaré),
  • Step 3. Créer une classe cliente qui (étend WebServiceGatewaySupport) et masque les appels aux opérations du ws
  • Step 4. Configurer Spring pour déclarer la classe client et le marshaller xml (au préalable, déclarer toutes les constantes dans WsConstantes.java),
  • Step 5. [Optionnelle] Écrire des classes utilitaires,
  • Step 6. [Optionnelle] Écrire un test JUnit,
  • Step 7. [Optionnelle] Écrire un Rest controller pour consommer ce ws.

 

 

Etape 1. Créer le projet démo

 

Créer le projet depuis la page http://start.spring.io avec trois briques: Web, WS, Lombok,

Télécharger le zip puis importer le projet dans l'IDE Eclipse ( STS par exemple).

 

Etape 2. Configurer plugin

 

Configurer le plugin qui génère les classes (domain object) à partir de wsdl permettant d'appeler le web service soap.

Ajouter ce plugin dans le bloc <plugins> du pom:

 <!-- tag::wsdl[] -->
 <plugin>                
  <groupId>org.jvnet.jaxb2.maven2</groupId>
    <artifactId>maven-jaxb2-plugin</artifactId>
    <version>0.13.2</version>
   <executions>
      <execution>
              <goals>
               <goal>generate</goal>
              </goals>
          </execution>
         </executions>
         <configuration>
                    <schemaLanguage>WSDL</schemaLanguage>
                    <generatePackage>fr.netapsys.abdou.ws.wsdl</generatePackage>
         <schemas>
          <schema>
                  <url>http://www.dneonline.com/calculator.asmx?WSDL</url>
          </schema>
       </schemas>
      </configuration>
      </plugin>
  <!-- end::wsdl[] -->
Ajout dans plugins tag

A noter la version 0.13.2 du plugin ce qui corrige certaines erreurs signalées ci-dessus.

Noter aussi la ligne 15 qui présente le nom du package dans lequel les classes java générées à partir de wsdl.

A la ligne 18, l'url wsdl est renseignée.

L'exécution de la commande maven clean install du projet maven va générer des classes java dans target/generated-sources/xjc.

 

Etape 3. Ecrire un client

Ajouter une classe java dont voici le code:

package fr.netapsys.abdou.ws.XXXXX;
import static fr.netapsys.abdou.ws.XXXXX.WsConstantes.*;
import o.s.ws.client.core.support.WebServiceGatewaySupport;
import o.s.ws.soap.client.core.SoapActionCallback;
import fr.netapsys.abdou.ws.wsdl.Add;
import fr.netapsys.abdou.ws.wsdl.AddResponse;

public class WebServiceClient extends WebServiceGatewaySupport {

  public AddResponse add(int number1, int number2) {
	Add request = new Add();
	request.setIntA(number1);
	request.setIntB(number2);
	AddResponse response =   (AddResponse) 	getWebServiceTemplate()
	.marshalSendAndReceive(
	  request,
	  new SoapActionCallback(OPERATION_NS_ADD)
		);		
	return response;
 }
 public int somme(int a, int b) {
	return add(a,b).getAddResult();
 }
}

 

Ici l'essentiel de ce qu'il faudrait retenir de ce guide mais avant de le détailler, voici la classe qui regroupe les constantes utilisées ici et ailleurs:

package fr.netapsys.abdou.ws.XXX.model;

public interface WsConstantes {

	final static String URL_WS="http://www.dneonline.com/calculator.asmx";
	
	final static String NAME_PACKAGE="fr.netapsys.abdou.ws.wsdl";
	//*operation add 
	final static String ROOT_NAMESPACE="http://tempuri.org";
	final static String  OPERATION_NS_ADD = ROOT_NAMESPACE+"/Add";
}

 

Vous constatez que la classe regroupe l'url wsdl, le nom du targetNamespace du webservice (ROOT_NAMESPACE) et celui de l'opération Add.

Revenons maintenant aux détails de la classe client ws:

 

  • La ligne 8 montre que notre classe cliente doit étendre la classe de spring nommée WebServiceGatewaySupport.
    • A la ligne 14, nous utilisons une template de spring (ici  WebServiceTemplate) pour marshaller, envoyer une requête soap et recevoir le retour. Tout ceci est regroupé dans l'appel de la méthode marshalSendAndReceive qui prend comme arguments:
      le premier est le message (payload)  de la requête soap,
      et le second est une instance de SoapActionCallback(OPERATION_NS_ADD) où la constante OPERATION_NS_ADD est définie dans l'interface WsConstantes.java.
  • A la ligne 21, nous avons donné une méthode simple somme de deux entiers qui retourne un entier masquant ainsi les appels au ws.

 

NOTE. Il est important de soigner ici les paramètres au risque de se retrouver avec des messages d'erreur de connexion ou permission denied!

 

Etape 4. Configurer spring

 

La configuration se fait dans la classe java annotée avec @SpringBootApplication et générée par la page de spring.io.

Pour cela, nous ajoutons ces déclarations de @Bean:

package fr.netapsys.abdou.ws.soap.client.demoWsSoapClient;
import o.s.bSpringApplication;
import o.s.b.autoconfigure.SpringBootApplication;
import o.s.c.annotation.Bean;
import o.s.oxm.jaxb.Jaxb2Marshaller;
import static fr.netapsys.abdou.ws.xxx.WsConstantes.*;
import lombok.extern.slf4j.Slf4j;
@SpringBootApplication
@Slf4j
public class DemoWsSoapClientApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoWsSoapClientApplication.class, args);
	}	
	@Bean
	public Jaxb2Marshaller marshaller() {
		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
		// package must match the package in the <generatePackage> specified in pom.xml
		marshaller.setContextPath(NAME_PACKAGE);
		return marshaller;
	}
	@Bean
	public WebServiceClient wsClient() {
		WebServiceClient client = new WebServiceClient();
		client.setDefaultUri(URL_WS);//url sans ?wsdl a la fin
      client.setMarshaller(marshaller());		  
      client.setUnmarshaller(marshaller());
     return client;
  }
}

 

Etape 5. Test Junit

Nous allons écrire un test JUnit pour tester ce qu'on a réalisé jusque là. Voici le code:

package fr.netapsys.abdou.ws.soap.client.demoWsSoapClient;
import org.junit.*;
import org.junit.runner.RunWith;
import o.s.b.factory.annotation.Autowired;
import o.s.b.test.context.SpringBootTest;
import o.s.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoWsSoapClientApplicationTests {

	@Autowired WebServiceClient wsClient;
	@Test
	public void testSomme2Nombres() {
		final int a=12, b=18;
		final int EXPECTED=a+b;
		int result = wsClient.somme(a, b);
		Assert.assertEquals(EXPECTED, result);
	}
}

C'est un test d'intégration passant avec injection par spring du client de l'étape 3.

Vous pouvez jeter un œil sur la console pour voir les appels soap:

.....
DEBUG [main] o.s.ws.client.core.WebServiceTemplate: 
Opening [o.s.ws.transport.http.HttpUrlConnection@1d035be3] to 
[http://www.dneonline.com/calculator.asmx]

DEBUG [main] o.s.ws.client.MessageTracing.sent: 
Sent request [SaajSoapMessage {http://tempuri.org/}Add]

DEBUG [main] o.s.ws.client.MessageTracing.received: 
Received response [SaajSoapMessage {http://tempuri.org/}AddResponse] 
for request [SaajSoapMessage {http://tempuri.org/}Add]
....
trace logs du test junit

 

Les lignes 3, 6 et 9 permettent de constater la connexion à l'url www.dneonline.com/calculator.asmx, l'envoi de la requête et enfin la réception de la réponse.

 

Etape 6. Classes utilitaires [Optionnelle]

Cette étape est optionnelle. Une seule classe DTO utilitaire permet juste de préparer les appels dans RestController ci-après.

Voici le code de la classe DTO:

package fr.netapsys.abdou.ws.soap.xxx.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ResultWS {
	int nombre1, nombre2;
	String operation;
	int resultat;
}

 

Etape 7. RestController qui consomme le ws [Optionnelle]

Cette étape optionnelle permet d'explorer un autre moyen de consommer le ws via un rest controller:

package fr.netapsys.abdou.ws.soap.client.demoWsSoapClient.controller;

import o.s.b.factory.annotation.Autowired;
import o.s.web.bind.annotation.RequestMapping;
import o.s.web.bind.annotation.RequestParam;
import o.s.web.bind.annotation.ResponseBody;
import o.s.web.bind.annotation.RestController;

import fr.netapsys.abdou.ws.soap....WebServiceClient;
import fr.netapsys.abdou.ws.soap.....ResultWS;

import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class RSController {
   @Autowired WebServiceClient wsClient;
    
   @RequestMapping(path="/somme")
   @ResponseBody
   public  ResultWS  callWs(@RequestParam(value="a") int a,
	@RequestParam(value="b") int b){
  	final int result = wsClient.somme(a,b);
	log.info("resultat de la somme de {} et {]",a,b,result);
	return new ResultWS(a,b,"Somme",result);
  	}
}

 

L'url http://localhost:8080/somme?a=100&b=2 doit afficher ce résultat:

{
nombre1: 100,
nombre2: 2,
operation: "Somme",
resultat: 102
}

 

Voilà je crois que le guide est complet et est en plus actualisé pour éviter les écueils que j'ai rencontrés.

N'hésitez pas à commenter ou à poser vos questions.

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *