Einführung in Apache CXF

Einführung in Apache CXF

1. Überblick

Apache CXF ist ein JAX-WS-kompatibles Framework.

Neben den in den JAX-WS-Standards definierten Funktionen bietet Apache CXF die Möglichkeit zur Konvertierung zwischen WSDL- und Java-Klassen, APIs zur Bearbeitung von unformatierten XML-Nachrichten, Unterstützung für JAX-RS, Integration in das Spring Framework usw.

Dieses Tutorial ist das erste einer Reihe von Tutorials zu Apache CXF, in denen grundlegende Merkmale des Frameworks vorgestellt werden. Es verwendet nur die JAX-WS-Standard-APIs im Quellcode, nutzt jedoch Apache CXF im Hintergrund, z. B. automatisch generierte WSDL-Metadaten und CXF-Standardkonfiguration.

2. Maven-Abhängigkeiten

Die für die Verwendung von Apache CXF erforderliche Schlüsselabhängigkeit istorg.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, bezogen 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 untercxf-rt-frontend-jaxws undcxf-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.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;
    }
}

Das Wichtigste, was hier zu beachten ist, ist das Vorhandensein des AttributsendpointInterface in der Annotation@WebService. 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 ImplementierungsklasseexampleImplweiterhin die folgende Endpunktschnittstelle, um zu verdeutlichen, dass alle deklarierten Methoden der Schnittstelle implementiert wurden. Dies ist jedoch optional:

@WebService
public interface example {
    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 vonMap, die von dergetStudents-Methode zurückgegeben wird,we need an adapter to convert the Map to a Java class that JAXB can use jedoch nicht direkt unterstützt.

Um Vertragselemente von ihrer Implementierung zu trennen, definieren wirStudent 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 wirStudent als Klasse deklarieren. Die Verwendung dieses Typs als Schnittstelle ist nur eine Demonstration der Verwendung von Anpassungsklassen.

Die Adapter werden im folgenden Abschnitt beschrieben.

4. Benutzerdefinierte Adapter

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

4.1. Schnittstellenadapter

So wird dieStudent-Schnittstelle definiert:

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

Diese Schnittstelle deklariert nur eine Methode, dieString zurückgibt, und gibtStudentAdapter als Anpassungsklasse an, um sich einem Typ zuzuordnen, der die JAXB-Bindung anwenden kann.

Die KlasseStudentAdapter 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 Anpassungsklasse muss dieXmlAdapter-Schnittstelle implementieren und die Implementierung für diemarshal- undunmarshal-Methoden bereitstellen. Die Methodemarshal transformiert 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). Dieunmarshal-Methode macht die Dinge umgekehrt.

Hier ist die Klassendefinition vonStudentImpl:

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

    // constructors, getter and setter
}

4.2. Map Adapter

DiegetStudents-Methode der Endpunktschnittstelle vonexamplegibt einMap zurück und gibt eine Anpassungsklasse an, um dieMap in einen Typ zu konvertieren, der von JAXB verarbeitet werden kann. Ähnlich wie die KlasseStudentAdapter muss diese Anpassungsklasse die Methodenmarshal undunmarshalder SchnittstelleXmlAdapter implementieren:

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 KlasseStudentMapAdapter ordnetMap<Integer, Student> dem WertetypStudentMap und dem WertetypStudentMapmit 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. Einsatz

5.1. Server Definition

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

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

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 einlong-Argument an dieThread.sleep-Methode übergeben.

5.2. Bereitstellung derServer

In diesem Tutorial verwenden wir das Pluginorg.codehaus.mojo:[.s1]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.example.cxf.introduction.Server
    

Die Konfiguration vonmainClassbezieht sich auf die KlasseServer, in der der Webdienstendpunkt veröffentlicht wird. Nachdem wir dasjava-Ziel dieses Plugins ausgeführt haben, haben wir can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/example?wsdl.

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.

Bitte beachten Sie, dass wir das Ziel vonexec:javaausführen müssen, um den Webdienstserver zu starten, bevor wir 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("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
}

Der folgende Initialisierungsblock wird verwendet, um das Feldservice vom Typjavax.xml.ws.Service zu initiieren, bevor ein Test ausgeführt wird:

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

Nach dem Hinzufügen der JUnit-Abhängigkeit zur POM-Datei können wir die Annotation@Beforewie im folgenden Codeausschnitt verwenden. Diese Methode wird vor jedem Test ausgeführt, um die Felder vonexampleerneut zu instanziieren:

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

Die VariableexampleProxy ist ein Proxy für den Webdienstendpunkt, währendexampleImpl nur ein einfaches Java-Objekt ist. Dieses Objekt wird verwendet, um Ergebnisse von Aufrufen von Remote-Endpunktmethoden über den Proxy mit Aufrufen von lokalen Methoden zu vergleichen.

Beachten Sie, dass die Instanz vonQNamedurch zwei Teile identifiziert wird: einen Namespace-URI und einen lokalen Teil. Wenn das ArgumentPORT_NAME vom TypQName der MethodeService.getPortweggelassen wird, geht Apache CXF davon aus, dass der Namespace-URI des Arguments der Paketname der Endpunktschnittstelle in umgekehrter Reihenfolge und ist Sein lokaler Teil ist der Schnittstellenname, an denPort angehängt ist. Dies entspricht genau dem Wert vonPORT_NAME.. Daher können wir dieses Argument in diesem Lernprogramm weglassen.

6.2. Testimplementierung

Der erste Testfall, den wir in diesem Unterabschnitt veranschaulichen, besteht darin, die Antwort zu validieren, die von einem Remote-Aufruf der Methodehelloauf dem Service-Endpunkt zurückgegeben wurde:

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

Es ist klar, dass die Remote-Endpunktmethode dieselbe Antwort wie die lokale Methode zurückgibt, was bedeutet, dass der Webdienst wie erwartet funktioniert.

Der nächste Testfall zeigt die Verwendung der MethodehelloStudent:

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

In diesem Fall sendet der Client einStudent-Objekt an den Endpunkt und erhält im Gegenzug eine Nachricht mit dem Namen des Schülers. Wie im vorherigen Testfall sind die Antworten von entfernten und lokalen Aufrufen gleich.

Der letzte Testfall, den wir hier zeigen, ist komplizierter. Wie von der Implementierungsklasse des Service-Endpunkts definiert, wird jedes Mal, wenn der Client die MethodehelloStudentauf dem Endpunkt aufruft, das übermittelte ObjektStudentin einem Cache gespeichert. Dieser Cache kann durch Aufrufen dergetStudents-Methode auf dem Endpunkt abgerufen werden. Der folgende Testfall bestätigt, dass der Inhalt desstudents-Caches das darstellt, was der Client an den Webdienst gesendet hat:

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

In diesem Tutorial wurde Apache CXF vorgestellt, ein leistungsstarkes Framework für die Arbeit mit Webdiensten in Java. Es konzentrierte sich auf die Anwendung des Frameworks als Standard-JAX-WS-Implementierung und nutzte gleichzeitig die spezifischen Funktionen des Frameworks zur Laufzeit.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie ina GitHub project.