Introduction à Apache CXF

1. Vue d'ensemble

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

En plus des fonctionnalités définies par les normes JAX-WS, Apache CXF offre la possibilité de conversion entre les classes WSDL 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 d'Apache CXF en arrière-plan, comme les métadonnées WSDL générées automatiquement et la configuration par défaut de CXF.

2. Dépendances de Maven

La dépendance clé nécessaire pour utiliser Apache CXF est org.apache.cxf: cxf - rt - frontend - jaxws . Cela fournit une implémentation JAX-WS pour remplacer le JDK intégré:

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

Notez que cet artefact contient un fichier nommé javax.xml.ws.spi.Provider dans le répertoire META-INF / services . Java VM examine la première ligne de ce fichier pour déterminer l'implémentation JAX-WS à utiliser. Dans ce cas, le contenu de la ligne est o rg.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 servlet pour publier le service, une autre dépendance est donc requise 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 consulter cxf-rt-frontend-jaxws et cxf-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.baeldung.cxf.introduction.Baeldung") public class BaeldungImpl implements Baeldung { 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' attribut endpointInterface 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 BaeldungImpl 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 Baeldung { 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, étant donné que JAXB ne prend pas directement en charge la liaison d'une carte , qui est renvoyée par la méthode getStudents , nous avons besoin d'un adaptateur pour convertir la carte en une classe Java que JAXB peut utiliser .

De plus, afin de séparer les éléments de contrat de leur implémentation, nous définissons Student 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éclarer Student comme une classe. L'utilisation de ce type comme interface n'est qu'une démonstration supplémentaire de l'utilisation des 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'une carte à l' aide de JAXB.

4.1. Adaptateur d'interface

Voici comment se définit l'interface Etudiant :

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

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

La classe StudentAdapter 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' interface XmlAdapter et fournir une implémentation pour les méthodes marshal et unmarshal . La méthode marshal 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éthode unmarshal fait les choses dans l'autre sens.

Voici la définition de la classe StudentImpl :

@XmlType(name = "Student") public class StudentImpl implements Student { private String name; // constructors, getter and setter }

4.2. Adaptateur de carte

Le getStudents procédé de Baeldung interface de noeud final renvoie une carte et indique une classe d'adaptation pour convertir la carte à un type qui peut être manipulé par JAXB. Semblable à la classe StudentAdapter , cette classe d'adaptation doit implémenter les méthodes marshal et unmarshal de l' interface XmlAdapter :

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 classe StudentMapAdapter mappe Map vers et depuis le type de valeur StudentMap avec la définition comme suit:

@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 du serveur

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

public class Server { public static void main(String args[]) throws InterruptedException { BaeldungImpl implementor = new BaeldungImpl(); String address = "//localhost:8080/baeldung"; 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, il doit être arrêté 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 long argument à la méthode Thread.sleep .

5.2. Déploiement du serveur

Dans ce tutoriel, nous utilisons le plugin org.codehaus.mojo: 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.baeldung.cxf.introduction.Server  

La configuration mainClass fait référence à la classe Server dans laquelle le point de terminaison du service Web est publié. Après avoir exécuté l' objectif java de ce plugin, nous pouvons vérifier le schéma WSDL généré automatiquement par Apache CXF en accédant à l'URL // localhost: 8080 / baeldung? Wsdl .

6. Cas de test

Cette section vous guide à travers les étapes d'écriture des cas de test utilisés pour vérifier le service Web que nous avons créé auparavant.

Veuillez noter que nous devons exécuter l' objectif exec: 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("//introduction.cxf.baeldung.com/", "Baeldung"); private static QName PORT_NAME = new QName("//introduction.cxf.baeldung.com/", "BaeldungPort"); private Service service; private Baeldung baeldungProxy; private BaeldungImpl baeldungImpl; // other declarations }

The following initializer block is used to initiate the service field of the javax.xml.ws.Service type prior to running any test:

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

After adding JUnit dependency to the POM file, we can use the @Before annotation as in the code snippet below. This method runs before every test to re-instantiate Baeldung fields:

@Before public void reinstantiateBaeldungInstances() { baeldungImpl = new BaeldungImpl(); baeldungProxy = service.getPort(PORT_NAME, Baeldung.class); }

The baeldungProxy variable is a proxy for the web service endpoint, while baeldungImpl is just a simple Java object. This object is used to compare results of invocations of remote endpoint methods through the proxy with invocations of local methods.

Note that a QName instance is identified by two parts: a Namespace URI and a local part. If the PORT_NAME argument, of the QName type, of the Service.getPort method is omitted, Apache CXF will assume that argument's Namespace URI is the package name of the endpoint interface in the reverse order and its local part is the interface name appended by Port, which is the exact same value of PORT_NAME. Therefore, in this tutorial we may leave this argument out.

6.2. Test Implementation

The first test case we illustrate in this sub-section is to validate the response returned from a remote invocation of the hello method on the service endpoint:

@Test public void whenUsingHelloMethod_thenCorrect() { String endpointResponse = baeldungProxy.hello("Baeldung"); String localResponse = baeldungImpl.hello("Baeldung"); assertEquals(localResponse, endpointResponse); }

It is clear that the remote endpoint method returns the same response as the local method, meaning the web service works as expected.

The next test case demonstrates the use of helloStudent method:

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

In this case, the client submits a Student object to the endpoint and receives a message containing the student's name in return. Like the previous test case, the responses from both remote and local invocations are the same.

The last test case that we show over here is more complicated. As defined by the service endpoint implementation class, each time the client invokes the helloStudent method on the endpoint, the submitted Student object will be stored in a cache. This cache can by retrieved by calling the getStudents method on the endpoint. The following test case confirms that content of the students cache represents what the client has sent to the web service:

@Test public void usingGetStudentsMethod_thenCorrect() { Student student1 = new StudentImpl("Adam"); baeldungProxy.helloStudent(student1); Student student2 = new StudentImpl("Eve"); baeldungProxy.helloStudent(student2); Map students = baeldungProxy.getStudents(); assertEquals("Adam", students.get(1).getName()); assertEquals("Eve", students.get(2).getName()); }

7. Conclusion

This tutorial introduced Apache CXF, a powerful framework to work with web services in Java. It focused on the application of the framework as a standard JAX-WS implementation, while still making use of the framework's specific capabilities at run-time.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans un projet GitHub.