• 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 04/02/2020

Créez un client et un web service Top-down

Nous allons nous attaquer ici à la méthode la plus utilisée pour la création de web services en entreprise. Cette méthode consiste à créer un contrat avant d'écrire le code. Le contrat est bien sûr un fichier WSDL qui décrit en détail votre service.

Cette approche a l'avantage de vous permettre d'exposer à tous le monde (et donc aux futurs clients) les caractéristiques de votre service pour validation. Vous pourrez alors répondre à des questions comme :

  • Est-ce que mon service correspond à ce qui a été demandé ?

  • Est-ce que les réponses que mon service renvoie sont satisfaisantes ?

  • Manque-t-il des opérations ?

  • Certaines opérations sont-elles déjà proposées par d'autres services ?

  • etc.

Car il faut savoir qu'un web service est en général plus complexe que celui que vous avez créé précédemment, il est donc préférable de valider d'abord, plutôt que de revenir sur un code complexe pour le remanier entièrement.

Cette méthode est aussi un passage obligé quand vous voudrez créer un client pour votre service. Vous n'aurez à ce moment-là rien d'autre qu'un grand WSDL indigeste. Hors de question de passer des heures à le décortiquer pour écrire le bon code qui enverra les bonne requêtes au bon format aux bonnes adresses. Nous allons voir comment générer automatiquement un client grâce à wsimport à partir d'un WSDL.

Mise en place du web service

Les étapes ici sont les mêmes que lorsque vous avez créé le premier web service avec la méthode Bottom-Up dans IntelliJ IDEA.

Vous allez simplement remplacer le GroupeID et l'artifactId par com.age et age.

Figure 28
Figure 29

Vous allez aussi nommer le projet age à la dernière étape.

Figure 30
Figure 30

Une fois votre projet créé, modifiez le pom.xml afin qu'il soit le même que pour le projet précédent.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.age</groupId>
  <artifactId>age</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <name>Pour connaître ton âge plus besoin d'envoyer "âge" au 8 12 12 ! </name>

  <build>
    <finalName>age</finalName>

    <plugins>

      <plugin>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
         <version>3.7.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxws-maven-plugin</artifactId>
      </plugin>

    </plugins>
  </build>
</project>

Pensez à supprimer le fichier web.xml sous WEB-INF.

N'oubliez pas sur IntelliJ de marquer le dossier java comme source et de créer la Class vide AgeService sous le dossier java.

Créer un fichier WSDL

La première chose est donc la création d'un fichier WSDL qui décrit notre service.

Dans la vie réelle, vous n'aurez pratiquement jamais à en créer à la main, des outils graphiques disponibles sur le marché ou dans les IDE permettent d'en créer, par exemple : WSDL Editor d'Eclipse ou encore Liquid.

Néanmoins, nous allons ici en créer un à la main afin que vous vous familiarisiez avec ce format. Sachez aussi que vous aurez, tôt ou tard, affaire à ce fichier car il est nécessaire de le comprendre pour débugger dans certains cas.

  • Dans le dossier resources, créez un fichier et nommez-le age.wsdl.

  • Dans le dossier java créez un Package et nommez le ageservice.

  • Créez ensuite dans le Package ageservice une Class java nommée AgeService.

Figure 31
Figure 31 

Pour rappel, voici la structure d'un document WSDL :

Figure 32
Figure 32

Dans le fichier age.wsdl :

  • Créez la balise < definitions > comme suit :

<?xml version="1.0" encoding="UTF-8"?>

<definitions
     xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
     targetNamespace="http://ageservice/"
     xmlns:tns="http://ageservice/"
     xmlns="http://schemas.xmlsoap.org/wsdl/"
     name="AgeService">

La balise definitions est la racine de tout WSDL, c'est-à-dire la balise qui va englober toutes les autres, elle définit les espaces de noms (namespaces) utilisés dans le document.

Ici, nous avons déclaré :

  • soap : définit des conventions dans le nommage et la structure de certains éléments comme body ou operation que nous verrons plus tard. Si vous voulez voir les éléments concernés, il suffit de vous rendre à l'URL indiquée.

  • targetNamespace : ceci est votre espace de nom qui fait référence à ce document même. Il est à noter que dans ce cas, on peut mettre n'importe quelle valeur, vous n'êtes pas obligé de mettre une vraie URL. Cela peut tout à fait être remplacé par un GUID (générateur GUID) par exemple xmlns:tns="cdf382d1-904d-4f7b-ab43-144623206c3a". D'ailleurs vous remarquerez que j'ai choisi un namescpace fictif http://ageservice/ qui n'est pas forcement une URL valide.

  • tns : c'est la même chose que targetNamespace, la différence c'est qu'on attribue un préfixe tns à ce namespace qui nous permettra plus tard d'y faire référence pour indiquer que tel tag appartient à notre document. Avec targetNamespace ceci n'est pas possible en absence justement de préfixe. Il est recommandé que targetNamespace et tns soient les mêmes.

  • xmlns : ceci est le namespace par défaut si jamais il y a un élément qui n'appartient à aucun.

  • name : un nom que vous donnez à votre service.

    • Créez la balise < types >

<types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"  targetNamespace="http://ageservice/">
            <xs:element name="getAge" type="tns:getAge"/>
            <xs:element name="getAgeResponse" type="tns:getAgeResponse"/>
            <xs:complexType name="getAge">
                <xs:sequence>
                    <xs:element name="arg0" type="xs:int" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="getAgeResponse">
                <xs:sequence>
                    <xs:element name="return" type="xs:string" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </types>

Ici vous allez définir les types de données qui vont transiter entre le client et votre service. WSDL ne définissant pas un typage limité, libre à vous de créer vos propres types grâce à la balise complexType.

  • Nous définissons un namespace xmlns:xs pour isoler dans celui-ci les balises que nous allons créer. Le but là encore est qu'il n'y ait pas de confusion dans les noms avec d'autres balises qu'on pourrait créer plus tard. Il sert aussi à montrer que notre structure doit correspondre à celle définie dans http://www.w3.org/2001/XMLSchem, ce qui permet plus tard la validation de éléments.

  • xs:element : nous définissons un élément getAge et un autre getAgeResponse.

  • xs:sequence : cette balise indique que les éléments qui composent ce complexType doivent apparaitre dans l'ordre. C'est la balise la plus utilisée car souvent quand on souhaite recevoir des paramètres à passer à une fonction, celles-ci doivent être dans l'ordre. Pour rappel les deux autres possibilités sont :

    • xs:all : l'ordre n'est pas important

    • xs:choice : un seul élément parmi une liste proposée est accepté.

  • xs:complexType nous revenons ici sur chaque élément pour détailler sa structure et son type de contenu. Ainsi pour getAge il est composé d'un int qu'on appelle arg0. minOccurs="0" (minimum occurrence) indique que cet élément est facultatif.

Au final ici nous avons un peu réinventé la roue on définissant un élément int qui sera plus tard utilisé en entrée de notre méthode qui calculera l'âge et un élément string qui sera la phrase retournée par celle-ci.

  • Créez la balise < message >

  <message name="getAge">
        <part name="parameters" element="tns:getAge"/>
  </message>

  <message name="getAgeResponse">
        <part name="parameters" element="tns:getAgeResponse"/>
  </message>

Les messages indiquent les paramètres qui seront passés plus tard aux opérations (les opérations étant l'équivalent des méthodes dans notre Class). Ne pas confondre donc message et type : type définit la nature d'un élément qu'on pourra utiliser comme on veut, alors que message fixe réellement lequel de ces éléments déclarés sera passé aux opérations.

  • Créez la balise < portType >

<portType name="AgeService">
        <operation name="getAge">
            <input message="tns:getAge"/>
            <output message="tns:getAgeResponse"/>
        </operation>
    </portType>

Ici nous indiquons enfin nos opérations possibles. Cela revient à indiquer quelles méthodes seront disponibles dans notre service et accessibles de l'extérieur. C'est l'équivalent de @WebMethod qu'on a vu plus tôt.

  • operation : nom qu'on souhaite donner à la méthode qui sera générée.

  • input : paramètre d'entrée de notre méthode, ici nous avons utilisé getAge qu'on a défini plus tôt.

    • output : paramètre de sortie.

    • Créez la balise < binding >

    <binding name="AgeServicePortBinding" type="tns:AgeService">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <operation name="getAge">
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
  • binding décrit concrètement comment les opérations définies dans portType seront invoquées sur le réseau.

  • soap:binding est utilisée pour indiquer que la communication se fera via le protocole SOAP.

  • transport="http://schemas.xmlsoap.org/soap/http" indique que les messages SOAP seront transmis via HTTP.

  • On indique enfin que pour l'opération getAge qu'on a définie dans portType, les paramètres en input et output seront encodés en literal, c'est-à-dire qu'ils seront mis en forme selon les définitions que nous avons spécifiées pour structurer les messages SOAP qu'on va renvoyer.

    • Créez la balise < service >

<service name="getAge">
        <port name="AgeServicePort" binding="tns:AgeServicePortBinding">
            <soap:address location="http://localhost:8080/Age/getAge"/>
        </port>
    </service>

Cette dernière balise indique littéralement l'URL pour appeler les opérations que nous avons définies. Dans ce cas, nous n'avons qu'une opération qui se trouve à http://localhost:8080/Age/getAge.

  • age.wsdl final

<?xml version="1.0" encoding="UTF-8"?>

<definitions
        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
        targetNamespace="http://ageservice/"
        xmlns:tns="http://ageservice/"
        xmlns="http://schemas.xmlsoap.org/wsdl/"
        name="AgeService">
    <types>
        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"  targetNamespace="http://ageservice/">
            <xs:element name="getAge" type="tns:getAge"/>
            <xs:element name="getAgeResponse" type="tns:getAgeResponse"/>
            <xs:complexType name="getAge">
                <xs:sequence>
                    <xs:element name="arg0" type="xs:int" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="getAgeResponse">
                <xs:sequence>
                    <xs:element name="return" type="xs:string" minOccurs="0"/>
                </xs:sequence>
            </xs:complexType>
        </xs:schema>
    </types>
    <message name="getAge">
        <part name="parameters" element="tns:getAge"/>
    </message>
    <message name="getAgeResponse">
        <part name="parameters" element="tns:getAgeResponse"/>
    </message>
    <portType name="AgeService">
        <operation name="getAge">
            <input message="tns:getAge"/>
            <output message="tns:getAgeResponse"/>
        </operation>
    </portType>
    <binding name="AgeServicePortBinding" type="tns:AgeService">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
        <operation name="getAge">
            <input>
                <soap:body use="literal"/>
            </input>
            <output>
                <soap:body use="literal"/>
            </output>
        </operation>
    </binding>
    <service name="getAge">
        <port name="AgeServicePort" binding="tns:AgeServicePortBinding">
            <soap:address location="http://localhost:8080/Age/getAge"/>
        </port>
    </service>
</definitions>

Maintenant que nous avons notre contrat, nous allons pouvoir utiliser l'outil wsimport pour générer toutes les classes nécessaires à l'implémentation de notre service.

Générer des artefacts AJAX-WS grâce à wsimport

wsimport est un outil inclus dans le JDK qui permet de générer les artefacts JAX-WS nécessaires pour créer un client ou un service à partir d'un WSDL. Ces artefacts sont tout simplement des classes et des interfaces qui font le boulot que nous avons fait plus tôt dans la démarche Bottom-up.

Utiliser wsimport

Si Java est installé, alors wsimport est aussi disponible ! Pour le vérifier, il suffit d'entrer la commande suivante :

wsimport -version

Résultat

wsimport version "2.2.9"
  • Les documents WSDL sont censés être dans un UDDI (vous vous souvenez ? :diable: ). Afin de s'affranchir des problèmes des chemins vers le WSDL, nous allons le placer dans un répertoire Github dont nous allons récupérer l'URL directe.

  • Commencez par vous placer dans le dossier age du projet.

cd /Chemin/Vers/Dossier/IdeaProjects/age
  • Générez les artefacts comme suit :

wsimport -Xnocompile -d ./src/main/java -p generated.ageservice https://raw.githubusercontent.com/AdamSoufiane/age/master/age.wsdl
  • -Xnocompile : indique que vous ne souhaitez pas que les Class générées soient compilées. En effet, nous les compilerons nous-mêmes grâce à Maven.

  • -d : indique le dossier dans le projet dans lequel vous souhaitez mettre les Class qui seront générées.

  • -p : indique à quel packet ces Class appartiendront. Dans ce cas, vous créez un packet spécialement pour celles-ci, appelé generated.ageservice.

    Pour connaître les autres paramètres, rendez vous dans la documentation.

Vous devriez obtenir cette arborescence :

Figure 33
Figure 33

Les fichiers générés

  • AgeService : il s'agit ici d'une interface qui représente ce que nous devrions implémenter. En d'autres termes, si vous ouvrez le fichier, vous verrez que cette interface définit une méthode getAge qui accepte comme argument arg0 que nous avons défini et retourne un String. Cette interface va nous permettre de créer l'implémentation dans laquelle nous allons définir la logique de notre service (dans ce cas le calcul de l'âge à partir d'une date de naissance).

  • GetAge et GetAgeResponse : ces Class représentent les complexType que nous avons créés précédemment dans la balise < types >. Ici elles n'ont pas une grande utilité, mais rappelez-vous de l'exemple que je vous ai donné avec la Class Client et nombreux attributs. Si nous avions déclaré un complexType qui définit un client avec tous ses attributs, nous aurions eu alors une belle Class Client.java avec toutes les méthodes et attributs prêts à l'emploi.

  • GetAge_Service : je vous parlerai de cette Class quand nous construirons le Client.

  • ObjectFactory : une Factory pour créer des instances des différentes Class générées à partir des complexType. Dans notre cas il s'agit des Class getAge et GetAgeResponse.

  • package-info.java : ce fichier permet en général de générer de la documentation pour les Packages en ajoutant des commentaires au-dessus des noms des Packages, mais dans notre cas sa principale fonction est de créer une association entre le Namespace http://ageservice/ que nous avions défini dans le WDSL et le package auquel il est associé dans les Class générées, à savoir generated.ageservice.

Créer l'implémentation

L'implémentation est très simple, il nous suffira d'implémenter l'Interface AgeService

package ageservice;


import javax.jws.WebMethod;
import javax.jws.WebService;
import java.math.BigInteger;


@WebService(serviceName = "getAge")

public class AgeService implements generated.ageservice.AgeService {

    @WebMethod
    public String getAge(Integer naissance) {

        return "Votre âge est de " + (2017-naissance) + "ans";
    }
}

C'est tout ! Vous venez de créer un web service en partant d'un WSDL. Évidemment, ici vous ne voyez pas forcement tous les avantages, car le code est court.

Mais imaginez que votre service gère des commandes et propose une dizaine de méthodes exposées et une dizaine de Class de type : Client, Commande, Paiement, chacun avec des attributs et méthodes. Dans ce cas-là, la puissance de wsimport apparaît clairement. En partant d'un WSDL, vous aurez toutes ces Class prêtes à l'emploi ainsi qu'un SEI formaté et annoté.

Packagez et changez l'URL de votre web service

Pour packager votre web service, il vous suffit de suivre les mêmes étapes que dans le service LifeLeft créé en Bottom-up. Pour rappel sur IntelliJ, il vous suffit de cliquer sur Install sous Lifecycle du panneau Maven.

Uploadez ensuite votre war dans Glassfish.

Vous avez dû remarquer que nous avons fixé l'URL sous la balise < service > dans notre WSDL à http://localhost:8080/Age/getAge. Or si vous vous rendez dans Applications sur votre serveur puis Launch vous remarquerez que votre service a une URL de ce type :

http://localhost:8080/age146146464161484/

Les chiffres après age sont un identifiant unique généré par Glassfish afin d'éviter que vous ayez deux services avec le même nom.

Comme nous ne souhaitons pas de chiffres dans notre URL afin qu'elle soit conforme à notre WSDL, nous allons les supprimer.

Rendez-vous dans Applications puis cliquez sur le nom de votre service age dans la colonne Name.

Sous Context Root: remplacez le tout par /Age puis validez.

Testez votre web service

Pour tester votre web service, vous pouvez procéder de la même manière que pour LeftLife.

Si vous avez suivi les mêmes noms que dans les exemples que je vous ai donnés, vous devriez avoir une URL de test comme celle-ci :

http://localhost:8080/Age/getAge?Tester

Entrez ensuite une date de naissance, vous devriez alors avoir une réponse comme celle-ci

Figure 34
Figure 34

Créer un client à partir d'un WSDL

Vous allez à présent créer un client pour votre web service LifeLeft.

Commencez par créer un autre projet vide exactement comme vous aviez fait avec LifeLeft.

GroupeId : com.leftlifeclient ArtifactId : leftlifeclient Project name : leftlifeclient

Ne créez par contre aucune Class.

  • Récupérez le WSDL de LifeLeft depuis Glassfish. Pour ce faire, allez dans Applications puis cliquez sur Launch en face de LifeLeft et cliquez sur le premier lien avec le port 8080.

    Ajoutez le nom de service que vous aviez défini dans serviceName de @WebService. Cela devrait vous donner une URL comme celle-ci :

    http://nomDeVotreOrdinateur.local:8080/lifeleft/LifeLeft

    Remarquez que j'ai changé l'URL de ce service également pour ne garder que lifeleft au lieu de lifeleft45656426146. Je vous invite à faire de même.

    Dans la page qui s'ouvre, vous avez l'adresse de votre WSDL à droite.

Figure 35
Figure 35

C'est cette adresse qui va nous permettre de générer notre client à partir du WSDL.

  • Comme nous l'avons fait plus tôt, placez-vous dans le dossier lifeleftclient depuis la ligne de commande.

  • Générez le client :

    wsimport -Xnocompile -d ./src/main/java -p generated.leftlife http://localhost:8080/lifeleft/LifeLeft?wsdl
    

    On utilise la même commande que précédemment, mais cette fois nous allons nous intéresser à l'implémentation d'un client à partir des Class générées.

    Vous obtenez alors ceci :

Figure 36
Figure 36
  •  

    Regardez la Class LifeLeft. Cette classe est appelée une Class proxy. Elle va vous permettre d'appeler le service distant LifeLeft comme s'il était local. JAX-WS vous cache tout ce qui concerne la formulation des requêtes SOAP, leur envoi via le protocole choisi (ici HTTP) et l'interprétation des réponses.

    L'annotation @WebServiceClient est une annotation utilisée par les générateurs comme wsimport pour localiser < service > dans le WSDL. Vous n'aurez pratiquement jamais à utiliser cette annotation.

    Cette Class hérite de la Class Service qui est une Class capable de fournir une représentation en Java d'un service à partir de son WSDL.

    S'ensuivent plusieurs définitions de constantes déclarant les URL et Namespaces qu'on a déclaré dans le WSDL.

    Ce qui nous intéresse ici est principalement la méthode getLifeLeftServicePort qui va nous donner accès à toutes les opérations déclarées dans le WSDL. Dans notre cas il y a une seule opération : anneesRestantesAVivre.

  • Utiliser le client

    Nous allons maintenant invoquer le code généré pour appeler notre service distant LifeLeft comme s'il était un simple package dans notre code.

    Créez une Class Main sous le dossier java.

    Invoquez le service :

    import generated.leftlife.LifeLeft;
    import generated.leftlife.LifeLeftService;
    
    public class Main {
    
      public static void main(String[] args) {
      LifeLeft lifeleft = new LifeLeft();
    
      LifeLeftService lifeleftsrv = lifeleft.getLifeLeftServicePort();
    
      String resultat = lifeleftsrv.anneeRestantesAVivre("john", "homme", 1980);
    
      System.out.println(resultat);
    
    } }
    • Ligne 8 : On crée une instance de la Class LifeLeft générée.

    • Ligne 10 : On demande une instance LifeLeftService qui contient l'opération anneesRestantesAVivre.

    • Ligne 11 : Nous appelons la méthode anneesRestantesAVivre en lui passant les paramètres nécessaires. C'est à ce moment-là que JAX-WS fera le nécessaire pour formuler une requête SOAP et l'envoyer à votre web service LifeLeft puis recevoir la réponse SOAP également et en extraire le résultat sous forme de String.

    • Ligne 13 : Écrire le résultat dans la sortie standard.

  • Tester le client

    Comme notre lifeleftcient n'est pas forcément un web service, vu qu'il ne propose aucune interaction avec le monde extérieur, nous n'avons pas besoin de le déployer dans un serveur comme Glassfish.

    Nous allons tout simplement le compiler et l'exécuter en local.

    Pour ce faire, faites un clic droit sur le main, puis cliquez sur Run 'Main.main()'.

Figure 37
Figure 37
  • Vous devriez avoir alors une réponse de ce type :

Figure 38
Figure 38

Voilà ! Vous venez de recevoir une réponse de votre web service. Vous n'avez eu à écrire aucune ligne de XML ni lire aucun WSDL.

Tout ça est très bien, mais vous ne trouvez pas que c'est un peu opaque ? On aimerait quand même voir ce qui se passe, quel message est envoyé et quel message est reçu, surtout en cas de bug !

Je vais donc vous apprendre dans la partie suivante à utiliser un outil magique : SoapUI ! :)

Exemple de certificat de réussite
Exemple de certificat de réussite