Apache CXF-støtte for RESTful Web Services

1. Oversikt

Denne opplæringen introduserer Apache CXF som et rammeverk som er i samsvar med JAX-RS-standarden, som definerer støtte til Java-økosystemet for REPResentational State Transfer (REST) ​​arkitektoniske mønster.

Spesielt beskriver den trinn for trinn hvordan du konstruerer og publiserer en RESTful webtjeneste, og hvordan du skriver enhetstester for å verifisere en tjeneste.

Dette er den tredje i en serie om Apache CXF; den første fokuserer på bruken av CXF som en JAX-WS fullstendig implementering. Den andre artikkelen gir en veiledning om hvordan du bruker CXF med våren.

2. Maven-avhengigheter

Den første nødvendige avhengigheten er org.apache.cxf: cxf- rt -frontend-jaxrs. Denne gjenstanden gir JAX-RS APIer samt en CXF-implementering:

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

I denne opplæringen bruker vi CXF til å lage en Server sluttpunkt for å publisere en nettjeneste i stedet for å bruke en servletbeholder. Derfor må følgende avhengighet inkluderes i Maven POM-filen:

 org.apache.cxf cxf-rt-transporterer-http-brygge 3.1.7 

Til slutt, la oss legge til HttpClient-biblioteket for å lette enhetstester:

 org.apache.httpkomponenter httpclient 4.5.2 

Her kan du finne den siste versjonen av cxf-rt-frontend-jaxrs avhengighet. Det kan også være lurt å referere til denne lenken for de nyeste versjonene av org.apache.cxf: cxf-rt-transporterer-http-brygge gjenstander. Endelig den siste versjonen av httpclient finner du her.

3. Ressursklasser og forespørsel om kartlegging

La oss begynne å implementere et enkelt eksempel; vi skal sette opp REST API med to ressurser Kurs og Student.

Vi begynner enkelt og går mot et mer komplekst eksempel når vi går.

3.1. Ressursene

Her er definisjonen av Student ressursklasse:

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

Legg merke til at vi bruker @XmlRootElement en kommentar for å fortelle JAXB at forekomster av denne klassen skal bli slått til XML.

Deretter kommer definisjonen av Kurs ressursklasse:

@XmlRootElement (name = "Course") kurs i offentlig klasse {private int id; privat strengnavn; private Listelever = nye ArrayList (); privat Student findById (int id) {for (Student student: studenter) {if (student.getId () == id) {retur student; }} returner null; }
 // standard getters og setter // standard er lik og hasCode implementeringer}

Til slutt, la oss implementere CourseRepository - som er rotressursen og fungerer som inngangspunkt for nettjenestressurser:

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

Legg merke til kartleggingen med @Sti kommentar. De CourseRepository er rotressursen her, så den er kartlagt for å håndtere alle URL-er fra og med kurs.

Verdien av @Produkter merknader brukes til å fortelle serveren å konvertere objekter som returneres fra metoder i denne klassen til XML-dokumenter før de sendes til klienter. Vi bruker JAXB her som standard siden ingen andre bindingsmekanismer er spesifisert.

3.2. Enkelt dataoppsett

Fordi dette er et enkelt eksempel på implementering, bruker vi data i minnet i stedet for en fullverdig vedvarende løsning.

Med det i tankene, la oss implementere noen enkle oppsettlogikker for å fylle ut noen data i systemet:

{Student student1 = ny student (); Studentstudent2 = ny student (); student1.setId (1); student1.setName ("Student A"); student2.setId (2); student2.setName ("Student B"); Liste kurs1Studenter = ny ArrayList (); course1Students.add (student1); course1Students.add (student2); Kurskurs1 = nytt Kurs (); Kurs2 = nytt kurs (); kurs1.setId (1); course1.setName ("HVIL med våren"); kurs1.settStudenter (kurs1Studenter); kurs2.setId (2); course2.setName ("Lær vårsikkerhet"); courses.put (1, course1); courses.put (2, course2); }

Metoder innen denne klassen som ivaretar HTTP-forespørsler blir dekket i neste underavsnitt.

3.3. API-et - Be om kartleggingsmetoder

La oss nå gå til implementeringen av selve REST API.

Vi skal begynne å legge til API-operasjoner - ved hjelp av @Sti kommentar - rett i ressursen POJOs.

Det er viktig å forstå at det er en betydelig forskjell fra tilnærmingen i et typisk vårprosjekt - der API-operasjonene ville bli definert i en kontroller, ikke på selve POJO.

La oss starte med kartleggingsmetoder som er definert i Kurs klasse:

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

Enkelt sagt påkalles metoden under håndtering forespørsler, betegnet av @FÅ kommentar.

Legg merke til den enkle syntaksen for å kartlegge Student ID baneparameter fra HTTP-forespørselen.

Vi bruker deretter bare findById hjelpermetode for å returnere tilsvarende Student forekomst.

Følgende metode håndterer POST forespørsler, angitt av @POST kommentar, ved å legge til mottatt Student motsette seg studenter liste:

@POST @Path ("") offentlig respons createStudent (studentstudent) {for (studentelement: studenter) {if (element.getId () == student.getId () {return Response.status (Response.Status.CONFLICT) .build ();}} students.add (student); return Response.ok (student) .build ();}

Dette returnerer a 200 OK svar hvis opprettingsoperasjonen var vellykket, eller 409 Konflikt hvis et objekt med innsendt id eksisterer allerede.

Vær også oppmerksom på at vi kan hoppe over @Sti kommentar siden verdien er en tom streng.

Den siste metoden tar vare på SLETT forespørsler. Det fjerner et element fra studenter liste hvis id er mottatt sti-parameter og returnerer et svar med OK (200) status. I tilfelle det ikke er noen elementer knyttet til det spesifiserte id, som antyder at det ikke er noe å fjerne, denne metoden returnerer et svar med Ikke funnet (404) status:

@DELETE @Path ("{studentId}") public Response deleteStudent (@PathParam ("studentId") int studentId) {Student student = findById (studentId); hvis (student == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } students.remove (student); returner Response.ok (). build (); }

La oss gå videre til å be om kartleggingsmetoder for CourseRepository klasse.

Følgende getCourse metoden returnerer a Kurs objekt som er verdien av en oppføring i kurs kart hvis nøkkel er mottatt kursId baneparameter for en be om. Internt sender metoden baneparametere til findById hjelpermetode for å gjøre jobben sin.

@GET @Path ("kurs / {kursId}") offentlig Kurs getCourse (@PathParam ("kursId") int kursId) {retur findById (kursId); }

Følgende metode oppdaterer en eksisterende oppføring av kurs kart, hvor kroppen til den mottatte SETTE forespørsel er inngangsverdien og kursId parameter er den tilhørende nøkkelen:

@PUT @Path ("courses / {courseId}") public Response updateCourse (@PathParam ("courseId") int courseId, Course course) {Course existingCourse = findById (courseId); hvis (eksisterendeCourse == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } hvis (eksisterendeCourse.equals (kurs)) {return Response.notModified (). build (); } courses.put (kursId, kurs); returner Response.ok (). build (); }

Dette updateCourse metoden returnerer et svar med OK (200) status hvis oppdateringen er vellykket, ikke endrer noe og returnerer a Ikke endret (304) svar hvis eksisterende og opplastede objekter har de samme feltverdiene. I tilfelle a Kurs eksempel med det gitte id finnes ikke i kurs kart, returnerer metoden et svar med Ikke funnet (404) status.

Den tredje metoden i denne rotressursklassen håndterer ikke HTTP-forespørsel direkte. I stedet delegerer den forespørsler til Kurs klasse der forespørsler håndteres ved å matche metoder:

@Path ("kurs / {courseId} / studenter") offentlig KursstiToStudent (@PathParam ("kursId") int kursId) {retur findById (kursId); }

Vi har vist metoder innen Kurs klasse som behandler delegerte forespørsler rett før.

4. Server Endepunkt

Denne seksjonen fokuserer på konstruksjonen av en CXF-server, som brukes til å publisere RESTful-nettjenesten hvis ressurser er avbildet i forrige avsnitt. Det første trinnet er å sette i gang en JAXRSServerFactoryBean objekt og angi rotressursklassen:

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

En ressursleverandør må da settes på fabrikkbønnen for å administrere livssyklusen til rotressursklassen. Vi bruker standard singleton-ressursleverandør som returnerer den samme ressursforekomsten til hver forespørsel:

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

Vi angir også en adresse for å indikere URL-en der nettjenesten publiseres:

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

Nå er det fabrikkBønne kan brukes til å lage en ny server som vil begynne å lytte etter innkommende tilkoblinger:

Server server = factoryBean.create ();

All koden ovenfor i denne delen skal pakkes inn i hoved- metode:

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

Påkallingen av dette hoved- metoden er presentert i seksjon 6.

5. Test tilfeller

Denne delen beskriver testtilfeller som ble brukt til å validere nettjenesten vi opprettet før. Disse testene validerer ressurstilstandene til tjenesten etter å ha svart på HTTP-forespørsler fra de fire mest brukte metodene, nemlig , POST, SETTE, og SLETT.

5.1. Forberedelse

Først blir to statiske felt deklarert innenfor testklassen, navngitt RestfulTest:

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

Før du kjører tester, lager vi en klient objekt, som brukes til å kommunisere med serveren og ødelegge den etterpå:

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

De klient forekomst er nå klar til å brukes av testtilfeller.

5.2. Forespørsler

I testklassen definerer vi to metoder å sende forespørsler til serveren som kjører webtjenesten.

Den første metoden er å få en Kurs eksempel gitt sin id i ressursen:

private Course getCourse (int courseOrder) kaster IOException {URL url = ny URL (BASE_URL + kursOrder); InputStream input = url.openStream (); Kurskurs = JAXB.unmarshal (ny InputStreamReader (input), Course.class); retur kurs; }

Det andre er å få en Student eksempel gitt ids av kurset og student i ressursen:

privat Student getStudent (int kursOrder, int studentOrder) kaster IOException {URL url = ny URL (BASE_URL + kursOrder + "/ studenter /" + studentOrder); InputStream input = url.openStream (); Studentstudent = JAXB.unmarshal (ny InputStreamReader (input), Student.class); returstudent; }

Disse metodene sender HTTP forespørsler til tjenestressursen, og deretter oppheve XML-svar på forekomster av de tilsvarende klassene. Begge brukes til å verifisere tjenestressurstilstander etter kjøring POST, SETTE, og SLETT forespørsler.

5.3. POST Forespørsler

Dette underavsnittet inneholder to testtilfeller for POST forespørsler, som illustrerer driften av nettjenesten når den lastes opp Student forekomst fører til en konflikt og når den er vellykket opprettet.

I den første testen bruker vi a Student gjenstand ubeskadiget fra conflict_student.xml filen, som ligger på klassestien med følgende innhold:

 2 Student B 

Slik konverteres innholdet til en POST forespørsel organ:

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

De Innholdstype header er satt til å fortelle serveren at innholdstypen til forespørselen er XML:

httpPost.setHeader ("Content-Type", "text / xml");

Siden den ble lastet opp Student objektet eksisterer allerede i det første Kurs for eksempel forventer vi at opprettelsen mislykkes og et svar med Konflikt (409) status returneres. Følgende kodebit bekrefter forventningen:

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

I neste test trekker vi ut kroppen til en HTTP-forespørsel fra en fil som heter created_student.xml, også på klassestien. Her er innholdet i filen:

 3 Student C 

I likhet med forrige testsak, bygger vi og utfører en forespørsel, og verifiserer deretter at en ny forekomst er opprettet:

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

Vi kan bekrefte nye tilstander for webtjenestens ressurs:

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

Dette er hva XML-svaret på en forespørsel om det nye Student objektet ser ut som:

  3 Student C 

5.4. SETTE Forespørsler

La oss starte med en ugyldig forespørsel om oppdatering, der Kurs objektet som oppdateres, eksisterer ikke. Her er innholdet i forekomsten som brukes til å erstatte et ikke-eksisterende Kurs objekt i nettjenestressursen:

 3 Apache CXF-støtte for RESTful 

Det innholdet er lagret i en fil som heter non_existent_course.xml på klassestien. Det ekstraheres og deretter brukes til å fylle ut kroppen til en SETTE forespørsel etter koden nedenfor:

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

De Innholdstype header er satt til å fortelle serveren at innholdstypen til forespørselen er XML:

httpPut.setHeader ("Innholdstype", "text / xml");

Siden vi med vilje sendte en ugyldig forespørsel om å oppdatere et objekt som ikke eksisterer, må en Ikke funnet (404) svar forventes å mottas. Svaret er validert:

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

I den andre prøvesaken for SETTE forespørsler, sender vi inn en Kurs objekt med de samme feltverdiene. Siden ingenting er endret i dette tilfellet, forventer vi at et svar med Ikke endret (304) status returneres. Hele prosessen er illustrert:

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

Hvor unchanged_course.xml er filen på klassestien som holder informasjonen som brukes til å oppdatere. Her er innholdet:

 1 HVIL med våren 

I den siste demonstrasjonen av SETTE forespørsler, utfører vi en gyldig oppdatering. Følgende er innholdet i changed_course.xml fil hvis innhold brukes til å oppdatere en Kurs forekomst i nettjenestressursen:

 2 Apache CXF-støtte for RESTful 

Slik er forespørselen bygget og utført:

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

La oss validere a SETTE forespørsel til serveren og validere en vellykket opplasting:

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

La oss bekrefte de nye tilstandene til nettjenestressursen:

Kurskurs = getCourse (2); assertEquals (2, course.getId ()); assertEquals ("Apache CXF-støtte for RESTful", kurs.getnavn ());

Følgende kodebit viser innholdet i XML-svaret når en GET-forespørsel om det tidligere lastede opp Kurs objektet sendes:

  2 Apache CXF-støtte for RESTful 

5.5. SLETT Forespørsler

La oss først prøve å slette et ikke-eksisterende Student forekomst. Operasjonen skal mislykkes og et tilsvarende svar med Ikke funnet (404) status forventes:

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

I den andre prøvesaken for SLETT forespørsler, oppretter vi, utfører og verifiserer en forespørsel:

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

Vi bekrefter nye tilstander for webtjenestressursen med følgende kodebit:

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

Deretter viser vi XML-svaret som mottas etter en forespørsel om den første Kurs objekt i nettjenestressursen:

  1 REST med vår 2 Student B 

Det er klart at den første Student er fjernet.

6. Testutførelse

Avsnitt 4 beskrev hvordan man oppretter og ødelegger en Server eksempel i hoved- metoden for RestfulServer klasse.

Det siste trinnet for å få serveren til å gå er å påkalle det hoved- metode. For å oppnå det er Exec Maven-pluginet inkludert og konfigurert i Maven POM-filen:

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

Den siste versjonen av dette pluginet finner du via denne lenken.

Under prosessen med å kompilere og pakke gjenstanden illustrert i denne opplæringen, utfører Maven Surefire-plugin automatisk alle tester som inngår i klasser som har navn som begynner eller slutter med Test. Hvis dette er tilfelle, bør pluginet konfigureres til å ekskludere disse testene:

 maven-surefire-plugin 2.19.1 ** / ServiceTest 

Med konfigurasjonen ovenfor, ServiceTest er ekskludert siden det er navnet på testklassen. Du kan velge hvilket som helst navn for den klassen, forutsatt at testene i den ikke kjøres av Maven Surefire-pluginet før serveren er klar for tilkobling.

For den siste versjonen av Maven Surefire-plugin, vennligst sjekk her.

Nå kan du utføre exec: java Målet er å starte RESTful-webserviceserveren og deretter kjøre testene ovenfor ved hjelp av en IDE. Tilsvarende kan du starte testen ved å utføre kommandoen mvn -Dtest = ServiceTest-test i en terminal.

7. Konklusjon

Denne opplæringen illustrerte bruken av Apache CXF som en JAX-RS-implementering. Den demonstrerte hvordan rammeverket kunne brukes til å definere ressurser for en RESTful webtjeneste og til å lage en server for publisering av tjenesten.

Implementeringen av alle disse eksemplene og kodebiter finner du i GitHub-prosjektet.


$config[zx-auto] not found$config[zx-overlay] not found