Introduction à Apache CXF

Introduction à Apache CXF

1. Vue d'ensemble

Apache CXF est un framework entièrement compatible JAX-WS.

Outre les fonctionnalités définies par les normes JAX-WS, Apache CXF offre la possibilité de convertir les classes Java et Java, les API utilisées pour manipuler les messages XML bruts, la prise en charge de JAX-RS, l'intégration avec Spring Framework, etc.

Ce tutoriel est le premier d'une série sur Apache CXF, présentant les caractéristiques de base du framework. Il utilise uniquement les API standard JAX-WS dans le code source, tout en tirant parti des avantages cachés d'Apache CXF, tels que les métadonnées WSDL générées automatiquement et la configuration par défaut de CXF.

2. Dépendances Maven

La dépendance de clé nécessaire pour utiliser Apache CXF estorg.apache.cxf:[.s1]cxfrtfrontendjaxws. This provides a JAX-WS implementation to replace the built-in JDK one:


    org.apache.cxf
    cxf-rt-frontend-jaxws
    3.1.6

Notice that this artifact contains a file named javax.xml.ws.spi.Provider inside the META-INF/services directory. Java VM looks at the first line of this file to determine the JAX-WS implementation to make use of. In this case, content of the line is org.apache.cxf.jaxws.spi.ProviderImpl, faisant référence à l'implémentation fournie par Apache CXF.

Dans ce didacticiel, nous n'utilisons pas de conteneur de servlets pour publier le service. Par conséquent, une autre dépendance est nécessaire pour fournir les définitions de type Java nécessaires:


    org.apache.cxf
    cxf-rt-transports-http-jetty
    3.1.6

Pour les dernières versions de ces dépendances, veuillez consultercxf-rt-frontend-jaxws etcxf-rt-transports-http-jetty dans le référentiel central Maven.

3. Point de terminaison du service Web

Commençons par la classe d'implémentation utilisée pour configurer le point de terminaison du service:

@WebService(endpointInterface = "com.example.cxf.introduction.example")
public class exampleImpl implements example {
    private Map students
      = new LinkedHashMap();

    public String hello(String name) {
        return "Hello " + name;
    }

    public String helloStudent(Student student) {
        students.put(students.size() + 1, student);
        return "Hello " + student.getName();
    }

    public Map getStudents() {
        return students;
    }
}

La chose la plus importante à noter ici est la présence de l'attributendpointInterface dans l'annotation@WebService. Cet attribut pointe vers une interface définissant un contrat abstrait pour le service Web.

Toutes les signatures de méthode déclarées dans l'interface du point de terminaison doivent être implémentées, mais ce n'est pas obligatoire pour implémenter l'interface.

Ici, la classe d'implémentation deexampleImpl implémente toujours l'interface de point de terminaison suivante pour indiquer clairement que toutes les méthodes déclarées de l'interface ont été implémentées, mais cela est facultatif:

@WebService
public interface example {
    public String hello(String name);

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map getStudents();
}

Par défaut, Apache CXF utilise JAXB comme architecture de liaison de données. Cependant, puisque JAXB ne prend pas directement en charge la liaison d'unMap, qui est renvoyé par la méthodegetStudents,we need an adapter to convert the Map to a Java class that JAXB can use.

De plus, afin de séparer les éléments de contrat de leur implémentation, nous définissonsStudent comme une interface et JAXB ne prend pas en charge directement les interfaces non plus, nous avons donc besoin d'un adaptateur supplémentaire pour gérer cela. En fait, pour plus de commodité, nous pouvons déclarerStudent comme une classe. L'utilisation de ce type comme interface n'est qu'une démonstration supplémentaire de l'utilisation de classes d'adaptation.

Les adaptateurs sont illustrés dans la section ci-dessous.

4. Adaptateurs personnalisés

Cette section illustre la manière d'utiliser les classes d'adaptation pour prendre en charge la liaison d'une interface Java et d'unMap à l'aide de JAXB.

4.1. Adaptateur d'interface

Voici comment se définit l'interfaceStudent:

@XmlJavaTypeAdapter(StudentAdapter.class)
public interface Student {
    public String getName();
}

Cette interface déclare une seule méthode renvoyant unString et spécifieStudentAdapter comme classe d'adaptation à mapper vers et depuis un type qui peut appliquer une liaison JAXB.

La classeStudentAdapter est définie comme suit:

public class StudentAdapter extends XmlAdapter {
    public StudentImpl marshal(Student student) throws Exception {
        if (student instanceof StudentImpl) {
            return (StudentImpl) student;
        }
        return new StudentImpl(student.getName());
    }

    public Student unmarshal(StudentImpl student) throws Exception {
        return student;
    }
}

Une classe d'adaptation doit implémenter l'interfaceXmlAdapter et fournir une implémentation pour les méthodesmarshal etunmarshal. La méthodemarshal transforme un type lié (Student, une interface que JAXB ne peut pas gérer directement) en un type valeur (StudentImpl, une classe concrète qui peut être traitée par JAXB). La méthodeunmarshal fait les choses dans l'autre sens.

Voici la définition de la classeStudentImpl:

@XmlType(name = "Student")
public class StudentImpl implements Student {
    private String name;

    // constructors, getter and setter
}

4.2. AdaptateurMap

La méthodegetStudents de l'interface de point de terminaisonexample renvoie unMap et indique une classe d'adaptation pour convertir lesMap en un type qui peut être géré par JAXB. Similaire à la classeStudentAdapter, cette classe d'adaptation doit implémenter les méthodesmarshal etunmarshal de l'interfaceXmlAdapter:

public class StudentMapAdapter
  extends XmlAdapter> {
    public StudentMap marshal(Map boundMap) throws Exception {
        StudentMap valueMap = new StudentMap();
        for (Map.Entry boundEntry : boundMap.entrySet()) {
            StudentMap.StudentEntry valueEntry  = new StudentMap.StudentEntry();
            valueEntry.setStudent(boundEntry.getValue());
            valueEntry.setId(boundEntry.getKey());
            valueMap.getEntries().add(valueEntry);
        }
        return valueMap;
    }

    public Map unmarshal(StudentMap valueMap) throws Exception {
        Map boundMap = new LinkedHashMap();
        for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) {
            boundMap.put(studentEntry.getId(), studentEntry.getStudent());
        }
        return boundMap;
    }
}

La classeStudentMapAdapter mappeMap<Integer, Student> vers et depuis le type de valeurStudentMap avec la définition suivante:

@XmlType(name = "StudentMap")
public class StudentMap {
    private List entries = new ArrayList();

    @XmlElement(nillable = false, name = "entry")
    public List getEntries() {
        return entries;
    }

    @XmlType(name = "StudentEntry")
    public static class StudentEntry {
        private Integer id;
        private Student student;

        // getters and setters
    }
}

5. Déploiement

5.1. Définition deServer

Afin de déployer le service Web décrit ci-dessus, nous utiliserons les API JAX-WS standard. Puisque nous utilisons Apache CXF, la structure fait un travail supplémentaire, par exemple. générer et publier le schéma WSDL. Voici comment le serveur de service est défini:

public class Server {
    public static void main(String args[]) throws InterruptedException {
        exampleImpl implementor = new exampleImpl();
        String address = "http://localhost:8080/example";
        Endpoint.publish(address, implementor);
        Thread.sleep(60 * 1000);
        System.exit(0);
    }
}

Une fois que le serveur est actif pendant un certain temps pour faciliter les tests, vous devez l’éteindre pour libérer les ressources système. Vous pouvez spécifier n'importe quelle durée de travail du serveur en fonction de vos besoins en passant un argumentlong à la méthodeThread.sleep.

5.2. Déploiement desServer

Dans ce tutoriel, nous utilisons le pluginorg.codehaus.mojo:[.s1]exec-maven-plugin pour instancier le serveur illustré ci-dessus et contrôler son cycle de vie. Ceci est déclaré dans le fichier Maven POM comme suit:


    org.codehaus.mojo
    exec-maven-plugin
    
        com.example.cxf.introduction.Server
    

La configurationmainClass fait référence à la classeServer où le point de terminaison du service Web est publié. Après avoir exécuté l'objectifjava de ce plugin, nous can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/example?wsdl.

6. Cas de test

Cette section vous guide tout au long des étapes pour écrire les scénarios de test utilisés pour vérifier le service Web que nous avons créé auparavant.

Veuillez noter que nous devons exécuter l'objectifexec:java pour démarrer le serveur de service Web avant d'exécuter un test.

6.1. Préparation

La première étape consiste à déclarer plusieurs champs pour la classe de test:

public class StudentTest {
    private static QName SERVICE_NAME
      = new QName("http://introduction.cxf.example.com/", "example");
    private static QName PORT_NAME
      = new QName("http://introduction.cxf.example.com/", "examplePort");

    private Service service;
    private example exampleProxy;
    private exampleImpl exampleImpl;

    // other declarations
}

Le bloc d'initialisation suivant est utilisé pour lancer le champservice du typejavax.xml.ws.Service avant d'exécuter un test:

{
    service = Service.create(SERVICE_NAME);
    String endpointAddress = "http://localhost:8080/example";
    service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
}

Après avoir ajouté la dépendance JUnit au fichier POM, nous pouvons utiliser l'annotation@Before comme dans l'extrait de code ci-dessous. Cette méthode s'exécute avant chaque test pour ré-instancier les champsexample:

@Before
public void reinstantiateexampleInstances() {
    exampleImpl = new exampleImpl();
    exampleProxy = service.getPort(PORT_NAME, example.class);
}

La variableexampleProxy est un proxy pour le point de terminaison du service Web, tandis queexampleImpl n'est qu'un simple objet Java. Cet objet est utilisé pour comparer les résultats d'appels de méthodes de point de terminaison distant via le proxy avec des appels de méthodes locales.

Notez qu'une instanceQName est identifiée par deux parties: un URI d'espace de nom et une partie locale. Si l'argumentPORT_NAME, de typeQName, de la méthodeService.getPort est omis, Apache CXF supposera que l'URI d'espace de nom de l'argument est le nom de package de l'interface du point de terminaison dans l'ordre inverse et sa partie locale est le nom de l'interface ajouté parPort, qui est exactement la même valeur dePORT_NAME. Par conséquent, dans ce tutoriel, nous pouvons laisser cet argument de côté.

6.2. Implémentation du test

Le premier cas de test que nous illustrons dans cette sous-section est de valider la réponse renvoyée par un appel distant de la méthodehello sur le point de terminaison de service:

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = exampleProxy.hello("example");
    String localResponse = exampleImpl.hello("example");
    assertEquals(localResponse, endpointResponse);
}

Il est clair que la méthode du point de terminaison distant renvoie la même réponse que la méthode locale, ce qui signifie que le service Web fonctionne comme prévu.

Le cas de test suivant montre l'utilisation de la méthodehelloStudent:

@Test
public void whenUsingHelloStudentMethod_thenCorrect() {
    Student student = new StudentImpl("John Doe");
    String endpointResponse = exampleProxy.helloStudent(student);
    String localResponse = exampleImpl.helloStudent(student);
    assertEquals(localResponse, endpointResponse);
}

Dans ce cas, le client soumet un objetStudent au point de terminaison et reçoit en retour un message contenant le nom de l'étudiant. Comme dans le cas précédent, les réponses des invocations distantes et locales sont les mêmes.

Le dernier cas de test que nous montrons ici est plus compliqué. Comme défini par la classe d'implémentation du noeud final de service, chaque fois que le client appelle la méthodehelloStudent sur le noeud final, l'objetStudent soumis sera stocké dans un cache. Ce cache peut être récupéré en appelant la méthodegetStudents sur le noeud final. Le cas de test suivant confirme que le contenu du cachestudents représente ce que le client a envoyé au service Web:

@Test
public void usingGetStudentsMethod_thenCorrect() {
    Student student1 = new StudentImpl("Adam");
    exampleProxy.helloStudent(student1);

    Student student2 = new StudentImpl("Eve");
    exampleProxy.helloStudent(student2);

    Map students = exampleProxy.getStudents();
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. Conclusion

Ce tutoriel présentait Apache CXF, un puissant framework permettant de travailler avec des services Web en Java. Il s'est concentré sur l'application du framework en tant qu'implémentation JAX-WS standard, tout en utilisant les capacités spécifiques du framework au moment de l'exécution.

L'implémentation de tous ces exemples et extraits de code se trouve dansa GitHub project.