Introduktion till Apache CXF

1. Översikt

Apache CXF är ett helt kompatibelt ramverk för JAX-WS.

Utöver funktioner som definierats av JAX-WS-standarder, erbjuder Apache CXF möjligheten att konvertera mellan WSDL- och Java-klasser, API: er som används för att manipulera råa XML-meddelanden, stöd för JAX-RS, integration med Spring Framework, etc.

Denna handledning är den första i en serie om Apache CXF, som introducerar grundläggande egenskaper hos ramverket. Den använder bara JAX-WS standard-API: er i källkoden medan den fortfarande utnyttjar Apache CXF bakom kulisserna, till exempel automatiskt genererade WSDL-metadata och CXF-standardkonfiguration.

2. Maven Beroenden

Det viktigaste beroendet för att använda Apache CXF är org.apache.cxf: cxf - rt - frontend - jaxws . Detta ger en JAX-WS-implementering för att ersätta den inbyggda JDK:

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

Observera att denna artefakt innehåller en fil med namnet javax.xml.ws.spi.Provider i META-INF / services- katalogen. Java VM tittar på den första raden i den här filen för att bestämma JAX-WS-implementeringen att använda. I detta fall är radens innehåll o rg.apache.cxf.jaxws.spi.ProviderImpl , med hänvisning till implementeringen från Apache CXF.

I denna handledning använder vi inte en servlet-behållare för att publicera tjänsten, därför krävs ett annat beroende för att tillhandahålla nödvändiga definitioner av Java-typ:

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

För de senaste versionerna av dessa beroenden, kolla in cxf-rt-frontend-jaxws och cxf-rt-transporter-http-brygga i Maven centrala arkiv.

3. Webbtjänstens slutpunkt

Låt oss börja med implementeringsklassen som används för att konfigurera tjänstens slutpunkt:

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

Det viktigaste som ska märkas här är närvaron av attributet endpointInterface i @WebService- anteckningen. Detta attribut pekar på ett gränssnitt som definierar ett abstrakt kontrakt för webbtjänsten.

Alla metodsignaturer som deklarerats i slutpunktsgränssnittet måste implementeras, men det krävs inte för att implementera gränssnittet.

Här implementerar BaeldungImpl- implementeringsklassen fortfarande följande slutpunktsgränssnitt för att göra det klart att alla deklarerade metoder för gränssnittet har implementerats, men att göra detta är valfritt:

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

Som standard använder Apache CXF JAXB som sin databindande arkitektur. Eftersom JAXB inte stöder bindning av en karta direkt , som returneras från getStudents- metoden, behöver vi dock en adapter för att konvertera kartan till en Java-klass som JAXB kan använda .

Dessutom, för att skilja kontraktselement från deras implementering, definierar vi Student som ett gränssnitt och JAXB stöder inte heller gränssnitt, och därför behöver vi ytterligare en adapter för att hantera detta. För att underlätta för oss kan vi faktiskt förklara Student som en klass. Användningen av den här typen som gränssnitt är bara ytterligare en demonstration av att använda anpassningsklasser.

Adaptrarna visas i avsnittet nedan.

4. Anpassade adaptrar

Detta avsnitt illustrerar sättet att använda anpassningsklasser för att stödja bindning av ett Java-gränssnitt och en karta med JAXB.

4.1. Gränssnittsadapter

Så här definieras studentgränssnittet :

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

Detta gränssnitt deklarerar endast en metod som returnerar en sträng och anger StudentAdapter som anpassningsklass för att kartlägga sig till och från en typ som kan tillämpa JAXB-bindning.

Den StudentAdapter klassen definieras enligt följande:

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

En anpassning klass måste implementera XmlAdapter gränssnittet och ge genomförandet av det marskalken och unmarshal metoder. Den marskalken metod omvandlar en bunden typ ( Student , ett gränssnitt som JAXB kan inte direkt handtag) till ett värde typ ( StudentImpl , en betong klass som kan bearbetas av JAXB). Den unmarshal metod gör saker tvärtom.

Här är klassdefinitionen StudentImpl :

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

4.2. karta Adapter

Den getStudents Metoden enligt Baeldung endpoint gränssnitt returnerar en karta och anger en anpassning klass för att omvandla Karta till en typ som kan hanteras av JAXB. I likhet med StudentAdapter klass måste denna anpassning klass implementera Marshal och unmarshal metoderna för XmlAdapter gränssnitt:

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

Den StudentMapAdapter klassen kartor Karta till och från StudentMap värdetypen med definitionen på följande sätt:

@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. Driftsättning

5.1. server Definition

För att distribuera den webbtjänst som diskuteras ovan kommer vi att använda standard JAX-WS API: er. Eftersom vi använder Apache CXF gör ramverket lite extra arbete, t.ex. genererar och publicerar WSDL-schemat. Så här definieras serviceservern:

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

När servern är aktiv en stund för att underlätta testningen bör den stängas av för att frigöra systemresurser. Du kan ange vilken arbetstid som helst för servern baserat på dina behov genom att skicka ett långt argument till Thread.sleep- metoden.

5.2. Driftsättning av servern

I denna handledning använder vi org.codehaus.mojo: exec -maven-plugin- plugin för att starta servern som illustreras ovan och styra dess livscykel. Detta förklaras i Maven POM-filen enligt följande:

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

Den mainClass konfiguration hänvisar till servern klass där webbtjänsten endpoint publiceras. Efter att ha kört Java- målet för detta plugin kan vi kolla in WSDL-schemat som automatiskt genereras av Apache CXF genom att komma åt URL // localhost: 8080 / baeldung? Wsdl .

6. Testfall

Detta avsnitt leder dig igenom steg för att skriva testfall som används för att verifiera webbtjänsten vi skapade tidigare.

Observera att vi måste köra exec: java- målet för att starta webbtjänstservern innan du kör något test.

6.1. Förberedelse

Det första steget är att deklarera flera fält för testklassen:

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.

Implementeringen av alla dessa exempel och kodavsnitt finns i ett GitHub-projekt.