Einführung in Apache CXF

1. Übersicht

Apache CXF ist ein JAX-WS-kompatibles Framework.

Zusätzlich zu den durch JAX-WS-Standards definierten Funktionen bietet Apache CXF die Möglichkeit der Konvertierung zwischen WSDL- und Java-Klassen, APIs zur Bearbeitung von XML-Rohnachrichten, die Unterstützung von JAX-RS, die Integration in das Spring Framework usw.

Dieses Tutorial ist das erste einer Reihe zu Apache CXF, in der grundlegende Merkmale des Frameworks vorgestellt werden. Es werden nur die JAX-WS-Standard-APIs im Quellcode verwendet, während Apache CXF hinter den Kulissen weiterhin genutzt wird, z. B. automatisch generierte WSDL-Metadaten und CXF-Standardkonfiguration.

2. Maven-Abhängigkeiten

Die Schlüsselabhängigkeit, die für die Verwendung von Apache CXF erforderlich ist, ist org.apache.cxf: cxf - rt - frontend - jaxws . Dies bietet eine JAX-WS-Implementierung als Ersatz für das integrierte JDK:

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

Beachten Sie, dass dieses Artefakt eine Datei mit dem Namen javax.xml.ws.spi.Provider im Verzeichnis META-INF / services enthält . Java VM untersucht in der ersten Zeile dieser Datei die JAX-WS-Implementierung, die verwendet werden soll. In diesem Fall lautet der Inhalt der Zeile o rg.apache.cxf.jaxws.spi.ProviderImpl und bezieht sich auf die von Apache CXF bereitgestellte Implementierung.

In diesem Lernprogramm wird kein Servlet-Container zum Veröffentlichen des Dienstes verwendet. Daher ist eine weitere Abhängigkeit erforderlich, um die erforderlichen Java-Typdefinitionen bereitzustellen:

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

Die neuesten Versionen dieser Abhängigkeiten finden Sie unter cxf-rt-frontend-jaxws und cxf-rt-transports-http-jetty im zentralen Maven-Repository.

3. Webdienst-Endpunkt

Beginnen wir mit der Implementierungsklasse, die zum Konfigurieren des Service-Endpunkts verwendet wird:

@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; } }

Das Wichtigste sei hier bemerkt , über das Vorhandensein des endpointInterface Attributs in der @WebService Anmerkung. Dieses Attribut verweist auf eine Schnittstelle, die einen abstrakten Vertrag für den Webdienst definiert.

Alle in der Endpunktschnittstelle deklarierten Methodensignaturen müssen implementiert werden, die Implementierung der Schnittstelle ist jedoch nicht erforderlich.

Hier implementiert die BaeldungImpl- Implementierungsklasse weiterhin die folgende Endpunktschnittstelle, um zu verdeutlichen, dass alle deklarierten Methoden der Schnittstelle implementiert wurden. Dies ist jedoch optional:

@WebService public interface Baeldung { public String hello(String name); public String helloStudent(Student student); @XmlJavaTypeAdapter(StudentMapAdapter.class) public Map getStudents(); }

Standardmäßig verwendet Apache CXF JAXB als Datenbindungsarchitektur. Da JAXB die Bindung einer Map , die von der getStudents- Methode zurückgegeben wird, nicht direkt unterstützt , benötigen wir einen Adapter, um die Map in eine Java-Klasse zu konvertieren, die JAXB verwenden kann .

Um Vertragselemente von ihrer Implementierung zu trennen, definieren wir Student als Schnittstelle und JAXB unterstützt auch keine Schnittstellen direkt. Daher benötigen wir einen weiteren Adapter, um dies zu behandeln. Der Einfachheit halber können wir den Schüler als Klasse deklarieren . Die Verwendung dieses Typs als Schnittstelle ist nur eine weitere Demonstration der Verwendung von Anpassungsklassen.

Die Adapter werden im Abschnitt rechts unten gezeigt.

4. Benutzerdefinierte Adapter

Dieser Abschnitt zeigt, wie Anpassungsklassen verwendet werden, um die Bindung einer Java-Schnittstelle und einer Map mithilfe von JAXB zu unterstützen.

4.1. Schnittstellenadapter

So wird die Student- Oberfläche definiert:

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

Diese Schnittstelle deklariert nur eine Methode, die einen String zurückgibt, und gibt StudentAdapter als Anpassungsklasse an, die sich einem Typ zuordnen soll, der die JAXB-Bindung anwenden kann.

Die StudentAdapter- Klasse ist wie folgt definiert:

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; } }

Eine Anpassung Klasse muss die Umsetzung XMLAdapter Schnittstelle und bieten Implementierung für den Marschall und Abstellungsmethoden. Die Marshall- Methode wandelt einen gebundenen Typ ( Student , eine Schnittstelle, die JAXB nicht direkt verarbeiten kann) in einen Werttyp ( StudentImpl , eine konkrete Klasse, die von JAXB verarbeitet werden kann) um. Die Unmarschall- Methode macht die Dinge umgekehrt.

Hier ist die StudentImpl- Klassendefinition:

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

4.2. Karte Adapter

Die getStudents- Methode der Baeldung- Endpunktschnittstelle gibt eine Map zurück und gibt eine Anpassungsklasse an, um die Map in einen Typ zu konvertieren, der von JAXB verarbeitet werden kann. Ähnlich wie bei der StudentAdapter Klasse muss diese Anpassung Klasse implementieren Marschall und Abstellungsmethoden des XMLAdapter Schnittstelle:

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; } }
    

Die StudentMapAdapter- Klasse ordnet Map dem und vom StudentMap-Werttyp mit der folgenden Definition zu:

@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. Bereitstellung

5.1. Server Definition

Um den oben beschriebenen Webdienst bereitzustellen, verwenden wir die Standard-JAX-WS-APIs. Da wir Apache CXF verwenden, erledigt das Framework einige zusätzliche Arbeiten, z. B. das Generieren und Veröffentlichen des WSDL-Schemas. So wird der Service-Server definiert:

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); } }

Nachdem der Server eine Weile aktiv war, um das Testen zu erleichtern, sollte er heruntergefahren werden, um Systemressourcen freizugeben. Sie können eine beliebige Arbeitsdauer für den Server basierend auf Ihren Anforderungen angeben, indem Sie ein langes Argument an die Thread.sleep- Methode übergeben.

5.2. Bereitstellung des Servers

In diesem Tutorial verwenden wir das Plugin org.codehaus.mojo: exec -maven- plugin, um den oben dargestellten Server zu instanziieren und seinen Lebenszyklus zu steuern. Dies wird in der Maven POM-Datei wie folgt deklariert:

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

Die Mainclass Konfiguration bezieht sich auf die Server - Klasse , wo der Web - Service - Endpunkt veröffentlicht wird. Nachdem Sie das Java- Ziel dieses Plugins ausgeführt haben, können Sie das von Apache CXF automatisch generierte WSDL-Schema überprüfen, indem Sie auf die URL // localhost: 8080 / baeldung? Wsdl zugreifen .

6. Testfälle

In diesem Abschnitt werden Sie durch die Schritte zum Schreiben von Testfällen geführt, mit denen der zuvor erstellte Webdienst überprüft wird.

Beachten Sie, dass wir das Ziel exec: java ausführen müssen, um den Webdienstserver zu starten, bevor Sie einen Test ausführen .

6.1. Vorbereitung

Der erste Schritt besteht darin, mehrere Felder für die Testklasse zu deklarieren:

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.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie in einem GitHub-Projekt.