Apache CXF Support til RESTful Web Services

1. Oversigt

Denne vejledning introducerer Apache CXF som en ramme, der er kompatibel med JAX-RS-standarden, som definerer understøttelse af Java-økosystemet til det REpresentational State Transfer (REST) ​​arkitektoniske mønster.

Specifikt beskriver den trin for trin, hvordan man konstruerer og udgiver en RESTful webtjeneste, og hvordan man skriver enhedstest for at verificere en tjeneste.

Dette er den tredje i en serie om Apache CXF; den første fokuserer på brugen af ​​CXF som en JAX-WS fuldt ud implementering. Den anden artikel giver en guide til, hvordan man bruger CXF med Spring.

2. Maven-afhængigheder

Den første krævede afhængighed er org.apache.cxf: cxf- rt -frontend-jaxrs. Denne artefakt leverer JAX-RS API'er samt en CXF-implementering:

 org.apache.cxf cxf-rt-frontend-jaxrs 3.1.7 

I denne vejledning bruger vi CXF til at oprette en Server slutpunkt for at udgive en webservice i stedet for at bruge en servletcontainer. Derfor skal følgende afhængighed inkluderes i Maven POM-filen:

 org.apache.cxf cxf-rt-transporterer-http-anløbsbro 3.1.7 

Lad os endelig tilføje HttpClient-biblioteket for at lette enhedstest:

 org.apache.httpkomponenter httpclient 4.5.2 

Her kan du finde den nyeste version af cxf-rt-frontend-jaxrs afhængighed. Du vil muligvis også henvise til dette link for de nyeste versioner af org.apache.cxf: cxf-rt-transporterer-http-anløbsbro artefakter. Endelig den seneste version af httpclient kan findes her.

3. Ressourceklasser og anmodningskortlægning

Lad os begynde at implementere et simpelt eksempel; vi opretter vores REST API med to ressourcer Rute og Studerende.

Vi starter simpelt og bevæger os mod et mere komplekst eksempel, når vi går.

3.1. Ressourcerne

Her er definitionen af Studerende ressource klasse:

@XmlRootElement (name = "Student") offentlig klasse elev {privat int id; privat strengnavn; // standard getters og setter // standard er lig med og hashCode implementeringer}

Bemærk, at vi bruger @XmlRootElement kommentar for at fortælle JAXB, at forekomster af denne klasse skal marcheres til XML.

Dernæst kommer definitionen af Rute ressource klasse:

@XmlRootElement (navn = "Kursus") offentlig klasse Kursus {privat int id; privat strengnavn; private List studerende = ny ArrayList (); privat Student findById (int id) {for (Student student: studerende) {if (student.getId () == id) {returstuderende; }} returner null; }
 // standard getters og setter // standard er lig med og hasCode implementeringer}

Lad os endelig implementere Kursusopbevaring - som er rodressourcen og fungerer som indgang til webservices ressourcer:

@Path ("kursus") @Produces ("tekst / xml") offentlig klasse CourseRepository {private Map kurser = ny HashMap (); // anmodningshåndteringsmetoder privat Course findById (int id) {for (Map.Entry-kursus: courses.entrySet ()) {if (course.getKey () == id) {return kursus.getValue (); }} returner null; }}

Bemærk kortlægningen med @Sti kommentar. Det Kursusopbevaring er rodressourcen her, så den er kortlagt til at håndtere alle URL'er begyndende med Rute.

Værdien af @Produces annotation bruges til at fortælle serveren at konvertere objekter, der returneres fra metoder inden for denne klasse, til XML-dokumenter, før de sendes til klienter. Vi bruger JAXB her som standard, da der ikke er angivet andre bindingsmekanismer.

3.2. Enkel dataopsætning

Da dette er et simpelt eksempel på implementering, bruger vi data i hukommelsen i stedet for en fuldgyldig vedvarende løsning.

Med det i tankerne, lad os implementere nogle enkle installationslogik for at udfylde nogle data i systemet:

{Studentstudent1 = ny elev (); Studerende student2 = ny studerende (); student1.setId (1); student1.setName ("Student A"); student2.setId (2); student2.setName ("Student B"); Liste kurs1Studenter = ny ArrayList (); course1Students.add (student1); course1Students.add (student2); Kursuskursus1 = nyt kursus (); Kursuskursus2 = nyt kursus (); kursus1.setId (1); course1.setName ("HVIL med foråret"); kursus1.setStudenter (kursus1Studenter); kursus2.setId (2); course2.setName ("Lær forårssikkerhed"); kurser.put (1, kursus1); kurser.put (2, kursus2); }

Metoder inden for denne klasse, der tager sig af HTTP-anmodninger, er dækket af det næste underafsnit.

3.3. API'en - Anmod kortlægningsmetoder

Lad os nu gå til implementeringen af ​​den faktiske REST API.

Vi begynder at tilføje API-operationer - ved hjælp af @Sti kommentar - lige i ressourcen POJO'er.

Det er vigtigt at forstå, at der er en væsentlig forskel fra fremgangsmåden i et typisk Spring-projekt - hvor API-operationer ville blive defineret i en controller, ikke i selve POJO.

Lad os starte med kortlægningsmetoder defineret inde i Rute klasse:

@GET @Path ("{studentId}") offentlig Student getStudent (@PathParam ("studentId") int studentId) {return findById (studentId); }

Kort sagt, metoden påberåbes under håndtering anmodninger, betegnet af @FÅ kommentar.

Bemærk den enkle syntaks ved kortlægning af studiekort styparameter fra HTTP-anmodningen.

Vi bruger derefter simpelthen findById hjælper metode til at returnere det tilsvarende Studerende eksempel.

Følgende metode håndterer STOLPE anmodninger, angivet af @STOLPE kommentar ved at tilføje det modtagne Studerende modsætter sig studerende liste:

@POST @Path ("") public Response createStudent (Student student) {for (Student element: studerende) {if (element.getId () == student.getId () {return Response.status (Response.Status.CONFLICT) .build ();}} students.add (student); returnere Response.ok (student) .build ();}

Dette returnerer en 200 OK svar, hvis oprettelsen var vellykket, eller 409 Konflikt hvis en genstand med den indsendte id findes allerede.

Bemærk også, at vi kan springe over @Sti kommentar, da dens værdi er en tom streng.

Den sidste metode tager sig af SLET anmodninger. Det fjerner et element fra studerende liste hvis id er parameteren modtaget sti og returnerer et svar med Okay (200) status. Hvis der ikke er nogen elementer, der er knyttet til det specificerede id, hvilket betyder, at der ikke er noget at fjerne, denne metode returnerer et svar med Ikke fundet (404) status:

@DELETE @Path ("{studentId}") offentlig reaktion deleteStudent (@PathParam ("studentId") int studentId) {Studentstudent = findById (studentId); hvis (student == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } studerende. fjern (studerende); returner Response.ok (). build (); }

Lad os gå videre til at anmode om kortlægningsmetoder for Kursusopbevaring klasse.

Det følgende getCourse metode returnerer a Rute objekt, der er værdien af ​​en post i kurser kort, hvis nøgle er modtaget kursus-id sti-parameter for en anmodning. Internt sender metoden stiparametre til findById hjælper metode til at gøre sit job.

@GET @Path ("courses / {courseId}") offentlig Course getCourse (@PathParam ("courseId") int courseId) {return findById (courseId); }

Følgende metode opdaterer en eksisterende post af kurser kort, hvor kroppen af ​​det modtagne SÆTTE anmodning er indtastningsværdien og kursus-id parameter er den tilknyttede nøgle:

@PUT @Path ("kurser / {kursusId}") offentlig reaktion updateCourse (@PathParam ("kursusId") int kursusId, kursus kursus) {Kursus eksisterendeCourse = findById (kursusId); hvis (eksisterendeCourse == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } hvis (eksisterendeCourse.equals (kursus)) {return Response.notModified (). build (); } kurser.put (kursus-id, kursus); returner Response.ok (). build (); }

Det her updateCourse metode returnerer et svar med Okay (200) status, hvis opdateringen er vellykket, ændrer ikke noget og returnerer a Ikke ændret (304) svar, hvis de eksisterende og uploadede objekter har de samme feltværdier. I tilfælde af a Rute eksempel med det givne id findes ikke i kurser kort, returnerer metoden et svar med Ikke fundet (404) status.

Den tredje metode i denne rodressourceklasse håndterer ikke nogen HTTP-anmodning direkte. I stedet delegerer den anmodninger til Rute klasse, hvor anmodninger håndteres ved at matche metoder:

@Path ("kurser / {courseId} / studerende") offentlig Course pathToStudent (@PathParam ("courseId") int courseId) {return findById (courseId); }

Vi har vist metoder inden for Rute klasse, der behandler delegerede anmodninger lige før.

4. Server Slutpunkt

Dette afsnit fokuserer på opbygningen af ​​en CXF-server, der bruges til at offentliggøre den RESTful-webservice, hvis ressourcer er afbildet i det foregående afsnit. Det første skridt er at instansiere en JAXRSServerFactoryBean objekt og indstil rodressource klassen:

JAXRSServerFactoryBean factoryBean = ny JAXRSServerFactoryBean (); factoryBean.setResourceClasses (CourseRepository.class);

En ressourceudbyder skal derefter indstilles på fabriksbønnen for at styre livscyklussen for rodressourceklassen. Vi bruger standardudbyderen af ​​singletonressourcer, der returnerer den samme ressourceinstans til hver anmodning:

factoryBean.setResourceProvider (nyt SingletonResourceProvider (nyt CourseRepository ()));

Vi indstiller også en adresse til at angive den URL, hvor webtjenesten offentliggøres:

factoryBean.setAddress ("// localhost: 8080 /");

Nu er det fabrikBønne kan bruges til at oprette en ny server der begynder at lytte efter indgående forbindelser:

Serverserver = factoryBean.create ();

Al koden ovenfor i dette afsnit skal pakkes ind i vigtigste metode:

public class RestfulServer {public static void main (String args []) kaster undtagelse {// kodestykke vist ovenfor}}

Påkaldelsen af ​​dette vigtigste metode er præsenteret i afsnit 6.

5. Test tilfælde

Dette afsnit beskriver testsager, der blev brugt til at validere den webservice, vi oprettede før. Disse tests validerer ressourcetilstande for tjenesten efter at have reageret på HTTP-anmodninger fra de fire mest anvendte metoder, nemlig , STOLPE, SÆTTEog SLET.

5.1. Forberedelse

For det første erklæres to statiske felter i testklassen, navngivet RestfulTest:

privat statisk streng BASE_URL = "// localhost: 8080 / baeldung / kurser /"; privat statisk CloseableHttpClient-klient;

Inden du kører tests opretter vi en klient objekt, som bruges til at kommunikere med serveren og ødelægge den bagefter:

@BeforeClass offentlig statisk ugyldighed createClient () {client = HttpClients.createDefault (); } @AfterClass offentlig statisk ugyldighed closeClient () kaster IOException {client.close (); }

Det klient instans er nu klar til brug i testsager.

5.2. Anmodninger

I testklassen definerer vi to metoder, der skal sendes anmodninger til serveren, der kører webservicen.

Den første metode er at få en Rute eksempel givet sin id i ressourcen:

privat kursus getCourse (int kursusbestilling) kaster IOException {URL url = ny URL (BASE_URL + kursusbestilling); InputStream input = url.openStream (); Kursus kursus = JAXB.unmarshal (ny InputStreamReader (input), Course.class); retur kursus; }

Det andet er at få en Studerende eksempel givet ids af kurset og studerende i ressourcen:

privat studerende getStudent (int kursusbestilling, int studentbestilling) kaster IOException {URL url = ny URL (BASE_URL + kursusbestilling + "/ studerende /" + studentbestilling); InputStream input = url.openStream (); Studerende = JAXB.unmarshal (ny InputStreamReader (input), Student.class); tilbagevendende studerende; }

Disse metoder sender HTTP anmodninger til serviceressourcen, og ophæv derefter XML-svar på forekomster af de tilsvarende klasser. Begge bruges til at verificere serviceresourcetilstande efter udførelse STOLPE, SÆTTEog SLET anmodninger.

5.3. STOLPE Anmodninger

Dette underafsnit indeholder to testsager til STOLPE anmodninger, der illustrerer driften af ​​webservicen, når den uploades Studerende eksempel fører til en konflikt, og når den oprettes med succes.

I den første test bruger vi en Studerende genstand uindåndet fra konflikt_student.xml fil, der er placeret på klassestien med følgende indhold:

 2 Studerende B 

Sådan konverteres dette indhold til en STOLPE anmodningsorgan:

HttpPost httpPost = ny HttpPost (BASE_URL + "1 / studerende"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("conflict_student.xml"); httpPost.setEntity (ny InputStreamEntity (resourceStream));

Det Indholdstype header er indstillet til at fortælle serveren, at anmodningens indholdstype er XML:

httpPost.setHeader ("Indholdstype", "tekst / xml");

Siden uploadet Studerende objekt findes allerede i det første Rute For eksempel forventer vi, at oprettelsen mislykkes, og et svar med Konflikt (409) status returneres. Følgende kodestykke bekræfter forventningen:

HttpResponse respons = client.execute (httpPost); assertEquals (409, respons.getStatusLine (). getStatusCode ());

I den næste test udpakker vi kroppen af ​​en HTTP-anmodning fra en fil med navnet created_student.xml, også på klassestien. Her er indholdet af filen:

 3 Elev C 

I lighed med den foregående testsag bygger vi og udfører en anmodning og verificerer derefter, at en ny forekomst er oprettet med succes:

HttpPost httpPost = ny HttpPost (BASE_URL + "2 / studerende"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("created_student.xml"); httpPost.setEntity (ny InputStreamEntity (resourceStream)); httpPost.setHeader ("Indholdstype", "tekst / xml"); HttpResponse respons = client.execute (httpPost); assertEquals (200, respons.getStatusLine (). getStatusCode ());

Vi bekræfter muligvis nye tilstande for webtjenestens ressource:

Studerende = getStudent (2, 3); assertEquals (3, student.getId ()); assertEquals ("Student C", student.getName ());

Dette er hvad XML-svaret på en anmodning om det nye Studerende objekt ligner:

  3 Elev C 

5.4. SÆTTE Anmodninger

Lad os starte med en ugyldig opdateringsanmodning, hvor Rute objekt, der opdateres, findes ikke. Her er indholdet af den forekomst, der bruges til at erstatte en ikke-eksisterende Rute objekt i webserviceresource:

 3 Apache CXF-support til RESTful 

Dette indhold gemmes i en fil, der kaldes non_existent_course.xml på klassestien. Det ekstraheres og derefter bruges til at befolke kroppen af ​​en SÆTTE anmodning ved hjælp af nedenstående kode:

HttpPut httpPut = ny HttpPut (BASE_URL + "3"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("non_existent_course.xml"); httpPut.setEntity (ny InputStreamEntity (resourceStream));

Det Indholdstype header er indstillet til at fortælle serveren, at anmodningens indholdstype er XML:

httpPut.setHeader ("Indholdstype", "tekst / xml");

Da vi med vilje sendte en ugyldig anmodning om at opdatere et ikke-eksisterende objekt, a Ikke fundet (404) svar forventes at blive modtaget. Svaret er valideret:

HttpResponse respons = client.execute (httpPut); assertEquals (404, respons.getStatusLine (). getStatusCode ());

I det andet test tilfælde for SÆTTE anmodninger, vi indsender en Rute objekt med de samme feltværdier. Da intet ændres i dette tilfælde, forventer vi, at et svar med Ikke ændret (304) status returneres. Hele processen illustreres:

HttpPut httpPut = ny HttpPut (BASE_URL + "1"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("unchanged_course.xml"); httpPut.setEntity (ny InputStreamEntity (resourceStream)); httpPut.setHeader ("Indholdstype", "tekst / xml"); HttpResponse respons = client.execute (httpPut); assertEquals (304, respons.getStatusLine (). getStatusCode ());

Hvor unchanged_course.xml er filen på klassestien, der holder oplysninger, der bruges til opdatering. Her er dens indhold:

 1 HVIL med foråret 

I den sidste demonstration af SÆTTE anmodninger, udfører vi en gyldig opdatering. Følgende er indholdet af ændret_løb.xml fil, hvis indhold bruges til at opdatere en Rute forekomst i webserviceresource:

 2 Apache CXF Support til RESTful 

Sådan bygges og udføres anmodningen:

HttpPut httpPut = ny HttpPut (BASE_URL + "2"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("changed_course.xml"); httpPut.setEntity (ny InputStreamEntity (resourceStream)); httpPut.setHeader ("Indholdstype", "tekst / xml");

Lad os validere a SÆTTE anmodning til serveren og validere en vellykket upload:

HttpResponse respons = client.execute (httpPut); assertEquals (200, respons.getStatusLine (). getStatusCode ());

Lad os kontrollere de nye tilstande for webtjenestens ressource:

Kursuskursus = getCourse (2); assertEquals (2, course.getId ()); assertEquals ("Apache CXF Support for RESTful", course.getName ());

Følgende kodestykke viser indholdet af XML-svaret, når en GET-anmodning om det tidligere uploadede Rute objektet sendes:

  2 Apache CXF-support til RESTful 

5.5. SLET Anmodninger

Lad os først prøve at slette et ikke-eksisterende Studerende eksempel. Operationen skal mislykkes, og et tilsvarende svar med Ikke fundet (404) status forventes:

HttpDelete httpDelete = ny HttpDelete (BASE_URL + "1 / studerende / 3"); HttpResponse respons = client.execute (httpDelete); assertEquals (404, respons.getStatusLine (). getStatusCode ());

I det andet test tilfælde for SLET anmodninger, opretter vi, udfører og bekræfter en anmodning:

HttpDelete httpDelete = ny HttpDelete (BASE_URL + "1 / studerende / 1"); HttpResponse respons = client.execute (httpDelete); assertEquals (200, respons.getStatusLine (). getStatusCode ());

Vi verificerer nye tilstande for webtjenestens ressource med følgende kodestykke:

Kursuskursus = getCourse (1); assertEquals (1, course.getStudents (). størrelse ()); assertEquals (2, course.getStudents (). get (0) .getId ()); assertEquals ("Student B", course.getStudents (). get (0) .getName ());

Dernæst viser vi XML-svaret, der modtages efter en anmodning om det første Rute objekt i webserviceresource:

  1 REST med Spring 2 Student B 

Det er klart, at den første Studerende er blevet fjernet.

6. Testudførelse

Afsnit 4 beskrev, hvordan man opretter og ødelægger en Server eksempel i vigtigste metode til RestfulServer klasse.

Det sidste trin for at få serveren til at køre er at påberåbe sig det vigtigste metode. For at opnå dette er Exec Maven-pluginet inkluderet og konfigureret i Maven POM-filen:

 org.codehaus.mojo exec-maven-plugin 1.5.0 com.baeldung.cxf.jaxrs.implementation.RestfulServer 

Den seneste version af dette plugin kan findes via dette link.

I processen med at kompilere og emballere den artefakt, der er illustreret i denne vejledning, udfører Maven Surefire-pluginet automatisk alle tests, der er lukket i klasser, der har navne der starter eller slutter med Prøve. Hvis dette er tilfældet, skal pluginet konfigureres til at udelukke disse tests:

 maven-surefire-plugin 2.19.1 ** / ServiceTest 

Med ovenstående konfiguration, ServiceTest er ekskluderet, da det er navnet på testklassen. Du kan vælge ethvert navn til den klasse, forudsat at test indeholdt deri ikke køres af Maven Surefire-pluginet, før serveren er klar til forbindelser.

Se den nyeste version af Maven Surefire-plugin her.

Nu kan du udføre exec: java mål at starte RESTful-webserviceserveren og derefter køre ovenstående tests ved hjælp af en IDE. Tilsvarende kan du starte testen ved at udføre kommandoen mvn -Dtest = ServiceTest test i en terminal.

7. Konklusion

Denne tutorial illustrerede brugen af ​​Apache CXF som en JAX-RS-implementering. Det demonstrerede, hvordan rammen kunne bruges til at definere ressourcer til en RESTful webtjeneste og til at oprette en server til udgivelse af tjenesten.

Implementeringen af ​​alle disse eksempler og kodestykker findes i GitHub-projektet.