Podpora Apache CXF pre RESTful webové služby

1. Prehľad

Tento výukový program predstavuje Apache CXF ako rámec kompatibilný so štandardom JAX-RS, ktorý definuje podporu ekosystému Java pre architektonický vzor REST (REpresentational State Transfer).

Konkrétne popisuje krok za krokom, ako vytvoriť a zverejniť webovú službu RESTful a ako napísať test jednotky na overenie služby.

Toto je tretí v rade na Apache CXF; prvý sa zameriava na použitie CXF ako plne kompatibilnej implementácie JAX-WS. Druhý článok poskytuje návod, ako používať CXF s Spring.

2. Maven závislosti

Prvá požadovaná závislosť je org.apache.cxf: cxf- rt-frontend-jaxrs. Tento artefakt poskytuje rozhrania JAX-RS API a implementáciu CXF:

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

V tomto tutoriále používame CXF na vytvorenie a Server koncový bod na publikovanie webovej služby namiesto použitia kontajnera servletu. Preto je potrebné do súboru Maven POM zahrnúť nasledujúcu závislosť:

 org.apache.cxf cxf-rt-transporty-http-mólo 3.1.7 

Na záver pridajme knižnicu HttpClient na uľahčenie jednotkových testov:

 org.apache.httpcomponents httpclient 4.5.2 

Tu nájdete najnovšiu verziu servera cxf-rt-frontend-jaxrs závislosť. Možno budete tiež chcieť odkazovať na tento odkaz, kde nájdete najnovšie verzie servera org.apache.cxf: cxf-rt-transporty-http-mólo artefakty. A konečne, najnovšia verzia httpclient nájdete tu.

3. Triedy zdrojov a mapovanie požiadaviek

Začnime implementovať jednoduchý príklad; ideme nastaviť naše REST API s dvoma zdrojmi Samozrejme a Študent.

Začneme jednoducho a postupujeme smerom k zložitejšiemu príkladu.

3.1. Zdroje

Tu je definícia Študent trieda zdrojov:

@XmlRootElement (name = "Študent") verejná trieda Študent {private int id; súkromné ​​meno reťazca; // štandardní vyhľadávači a nastavovatelia // štandardné implementácie equals a hashCode}

Všimnite si, že používame @XmlRootElement anotácia, ktorá má povedať JAXB, že inštancie tejto triedy by mali byť zaradené do XML.

Ďalej nasleduje definícia Samozrejme trieda zdrojov:

@XmlRootElement (name = "Course") verejná trieda Course {private int id; súkromné ​​meno reťazca; private List students = new ArrayList (); private Student findById (int id) {for (Student student: students) {if (student.getId () == id) {return student; }} return null; }
 // štandardné vyhľadávače a nastavovače // štandardné sa rovná a implementácia hasCode}

Nakoniec poďme implementovať Repozitár kurzu - ktorý je koreňovým prostriedkom a slúži ako vstupný bod do zdrojov webovej služby:

@Path ("kurz") @Produces ("text / xml") verejná trieda CourseRepository {súkromné ​​mapové kurzy = nový HashMap (); // vyžiadať spôsoby manipulácie súkromné ​​Course findById (int id) {for (Map.Entry course: courses.entrySet ()) {if (course.getKey () == id) {return course.getValue (); }} return null; }}

Všimnite si mapovanie pomocou @ Cesta anotácia. The Repozitár kurzu je tu koreňový zdroj, takže je mapovaný tak, aby spracovával všetky adresy URL začínajúce sa samozrejme.

Hodnota @Produkty anotácia sa používa na to, aby informovala server o prevedení objektov vrátených z metód v rámci tejto triedy na dokumenty XML pred ich odoslaním klientom. Používame tu JAXB ako predvolený, pretože nie sú zadané žiadne ďalšie mechanizmy väzby.

3.2. Jednoduché nastavenie údajov

Pretože sa jedná o jednoduchý príklad implementácie, namiesto plnohodnotného trvalého riešenia používame údaje v pamäti.

S ohľadom na to implementujme jednoduchú logiku nastavenia na vyplnenie niektorých údajov do systému:

{Student student1 = new Student (); Študent student2 = nový Študent (); student1.setId (1); student1.setName ("Študent A"); student2.setId (2); student2.setName ("Študent B"); Zoznam kurzov1Studenti = nový ArrayList (); course1Students.add (student1); kurz1Studenti.pridat (student2); Kurz kurzu1 = nový kurz (); Kurz Course2 = nový kurz (); course1.setId (1); course1.setName ("REST s jarou"); course1.setStudents (course1Students); course2.setId (2); course2.setName ("Learn Spring Security"); courses.put (1, course1); courses.put (2, course2); }

Metódam v tejto triede, ktoré sa starajú o požiadavky HTTP, sa venuje nasledujúca pododdiel.

3.3. API - metódy mapovania požiadaviek

Poďme teraz k implementácii skutočného REST API.

Začneme pridávať operácie API - pomocou @ Cesta anotácia - priamo v zdroji POJOs.

Je dôležité pochopiť, že ide o významný rozdiel od prístupu v typickom jarnom projekte - kde by operácie API boli definované v radiči, nie na samotnom POJO.

Začnime s mapovacími metódami definovanými vo vnútri Samozrejme trieda:

@GET @Path ("{studentId}") public Student getStudent (@PathParam ("studentId") int studentId) {návrat findById (studentId); }

Jednoducho povedané, metóda sa vyvoláva pri manipulácii ZÍSKAJTE žiadosti, označené @ ZÍSKAŤ anotácia.

Všimli ste si jednoduchú syntax mapovania súboru Študentská karta parameter cesty z požiadavky HTTP.

Potom jednoducho používame findById pomocná metóda na vrátenie zodpovedajúcich Študent inštancia.

Nasledujúca metóda sa zaoberá POST žiadosti, označené @POST anotáciu pridaním prijatej Študent namietať proti študentov zoznam:

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

Týmto sa vráti a 200 OK odpoveď, ak bola operácia vytvorenia úspešná, alebo 409 Konflikt ak je predmetom s predloženým id už existuje.

Upozorňujeme tiež, že môžeme vynechať @ Cesta anotácia, pretože jej hodnota je prázdny reťazec.

Posledná metóda sa stará ODSTRÁNIŤ žiadosti. Odstráni prvok z študentov zoznam ktorých id je prijatý parameter cesty a vráti odpoveď s Ok (200) stav. V prípade, že so zadaným nie sú spojené žiadne prvky id, čo znamená, že nie je potrebné nič odstraňovať, táto metóda vráti odpoveď s Nenájdené Stav (404):

@DELETE @Path ("{studentId}") verejná odpoveď deleteStudent (@PathParam ("studentId") int studentId) {Student student = findById (studentId); if (student == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } študenti.odstrániť (študent); vrátiť Response.ok (). build (); }

Prejdime k požiadavkám na mapovacie metódy Repozitár kurzu trieda.

Nasledujúci getCourse metóda vracia a Samozrejme objekt, ktorý je hodnotou záznamu v kurzov mapa, ktorej kľúč je prijatý CourseId parameter cesty a ZÍSKAJTE žiadosť. Metóda interne odosiela parametre cesty do súboru findById pomocná metóda na vykonávanie svojej práce.

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

Nasledujúca metóda aktualizuje existujúci záznam súboru kurzov mapa, kde je telo prijatého PUT požiadavka je vstupná hodnota a CourseId parameter je priradený kľúč:

@PUT @Path ("courses / {courseId}") public Response updateCourse (@PathParam ("courseId") int courseId, kurz kurzu) {Course existingCourse = findById (courseId); if (existingCourse == null) {return Response.status (Response.Status.NOT_FOUND) .build (); } if (existingCourse.equals (course)) {return Response.notModified (). build (); } courses.put (courseId, course); vrátiť Response.ok (). build (); }

Toto updateCourse metóda vráti odpoveď s Ok (200) stav, ak je aktualizácia úspešná, nezmení nič a vráti a Nezmenené (304) Odozva, ak majú existujúce a nahrané objekty rovnaké hodnoty poľa. V prípade a Samozrejme inštancie s daným id sa nenachádza v kurzov mapa, metóda vráti odpoveď s Nenájdené (404) stav.

Tretia metóda tejto triedy koreňových prostriedkov nespracováva priamo žiadnu požiadavku HTTP. Namiesto toho deleguje žiadosti na Samozrejme trieda, kde sa požiadavky vybavujú metódami párovania:

@Path ("courses / {courseId} / students") public Course pathToStudent (@PathParam ("courseId") int courseId) {return findById (courseId); }

Ukázali sme metódy v rámci Samozrejme triedy, ktorá spracúva delegované požiadavky priamo pred

4. Server Koncový bod

Táto časť sa zameriava na konštrukciu servera CXF, ktorý sa používa na publikovanie webovej služby RESTful, ktorej zdroje sú zobrazené v predchádzajúcej časti. Prvým krokom je vytvoriť inštanciu a JAXRSServerFactoryBean objekt a nastavte triedu koreňových prostriedkov:

JAXRSServerFactoryBean factoryBean = nový JAXRSServerFactoryBean (); factoryBean.setResourceClasses (CourseRepository.class);

Potom je potrebné nastaviť poskytovateľa zdrojov na továrenské zrno, aby riadil životný cyklus triedy koreňových prostriedkov. Používame predvoleného poskytovateľa prostriedkov typu singleton, ktorý vracia rovnakú inštanciu prostriedku pre každú požiadavku:

factoryBean.setResourceProvider (nový SingletonResourceProvider (nový CourseRepository ()));

Nastavili sme tiež adresu na označenie adresy URL, na ktorej je webová služba zverejnená:

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

Teraz factoryBean možno použiť na vytvorenie nového server ktoré začne počúvať prichádzajúce spojenia:

Server server = factoryBean.create ();

Celý kód uvedený v tejto časti by mal byť zabalený do súboru hlavný metóda:

public class RestfulServer {public static void main (String args []) throws Exception {// fragmenty kódu zobrazené vyššie}}

Vyvolanie tohto hlavný Táto metóda je uvedená v časti 6.

5. Testovacie prípady

Táto časť popisuje testovacie prípady použité na overenie webovej služby, ktorú sme vytvorili predtým. Tieto testy overujú stavy prostriedkov služby po odpovedaní na požiadavky HTTP štyroch najbežnejšie používaných metód, a to ZÍSKAJTE, POST, PUTa ODSTRÁNIŤ.

5.1. Príprava

Najskôr sa v rámci testovacej triedy deklarujú dve statické polia s názvom RestfulTest:

private static String BASE_URL = "// localhost: 8080 / baeldung / courses /"; súkromný statický klient CloseableHttpClient;

Pred spustením testov vytvoríme a zákazník objekt, ktorý slúži na komunikáciu so serverom a následnú likvidáciu:

@BeforeClass public static void createClient () {client = HttpClients.createDefault (); } @AfterClass public static void closeClient () vyvolá IOException {client.close (); }

The zákazník inštancia je teraz pripravená na použitie v testovacích prípadoch.

5.2. ZÍSKAJTE Žiadosti

V testovacej triede definujeme dve metódy na odoslanie ZÍSKAJTE požiadavky na server, na ktorom je spustená webová služba.

Prvou metódou je získanie a Samozrejme príklad vzhľadom na jeho id v zdroji:

private Course getCourse (int courseOrder) throws IOException {URL url = new URL (BASE_URL + courseOrder); InputStream vstup = url.openStream (); Kurz kurzu = JAXB.unmarshal (nový InputStreamReader (vstup), Course.class); spiatočný kurz; }

Druhým je získanie a Študent príklad vzhľadom na idKurz a študent v zdroji:

private Student getStudent (int courseOrder, int studentOrder) hodí IOException {URL url = nová URL (BASE_URL + courseOrder + "/ students /" + studentOrder); InputStream vstup = url.openStream (); Študent študent = JAXB.unmarshal (nový InputStreamReader (vstup), Student.class); návrat študent; }

Tieto metódy odosielajú protokol HTTP ZÍSKAJTE požiadavky na zdroj služby, potom unmarshal XML odpovede na inštancie zodpovedajúcich tried. Oba sa používajú na overenie stavov zdrojov služby po vykonaní POST, PUTa ODSTRÁNIŤ žiadosti.

5.3. POST Žiadosti

Táto pododdiel obsahuje dva testovacie prípady pre POST požiadavky, ilustrujúce operácie webovej služby pri ich načítaní Študent inštancia vedie ku konfliktu a keď sa úspešne vytvorí.

V prvom teste používame a Študent objekt nezaradený z conflict_student.xml súbor, ktorý sa nachádza na ceste triedy s týmto obsahom:

 2 Študent B 

Týmto spôsobom sa tento obsah prevádza na a POST orgán žiadosti:

HttpPost httpPost = nový HttpPost (BASE_URL + "1 / študenti"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("conflict_student.xml"); httpPost.setEntity (nový InputStreamEntity (resourceStream));

The Druh obsahu Hlavička je nastavená tak, aby informovala server, že typ obsahu požiadavky je XML:

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

Od nahratia Študent objekt už v prvom existuje Samozrejme Napríklad očakávame, že vytvorenie zlyhá a odpoveď s Konflikt (409) stav je vrátený. Nasledujúci úryvok kódu overuje očakávanie:

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

V ďalšom teste extrahujeme telo požiadavky HTTP zo súboru s názvom created_student.xml, tiež na triednej ceste. Tu je obsah súboru:

 3 Študent C 

Podobne ako v predchádzajúcom testovacom prípade, zostavíme a vykonáme požiadavku a potom overíme, či je úspešne vytvorená nová inštancia:

HttpPost httpPost = nový HttpPost (BASE_URL + "2 / študenti"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("created_student.xml"); httpPost.setEntity (nový InputStreamEntity (resourceStream)); httpPost.setHeader ("Content-Type", "text / xml"); HttpResponse response = client.execute (httpPost); assertEquals (200, response.getStatusLine (). getStatusCode ());

Môžeme potvrdiť nové stavy zdroja webových služieb:

Študent študent = getStudent (2, 3); assertEquals (3, student.getId ()); assertEquals ("Študent C", student.getName ());

To je odpoveď XML na žiadosť o nový Študent objekt vyzerá ako:

  3 Študent C 

5.4. PUT Žiadosti

Začnime s neplatnou požiadavkou na aktualizáciu, kde Samozrejme aktualizovaný objekt neexistuje. Tu je obsah inštancie použitej na nahradenie neexistujúceho Samozrejme objekt v prostriedku webovej služby:

 3 Podpora Apache CXF pre RESTful 

Tento obsah je uložený v súbore s názvom non_existent_course.xml na triednej ceste. Extrahuje sa a potom sa použije na naplnenie tela a PUT žiadosť o kód uvedený nižšie:

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

The Druh obsahu Hlavička je nastavená tak, aby informovala server, že typ obsahu požiadavky je XML:

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

Keďže sme zámerne poslali neplatnú žiadosť o aktualizáciu neexistujúceho objektu, a Nenájdené (404) sa očakáva prijatie odpovede. Odpoveď je overená:

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

V druhom testovacom prípade pre PUT žiadosti, podávame a Samozrejme objekt s rovnakými hodnotami poľa. Pretože sa v tomto prípade nič nemení, očakávame, že odpoveď s Nezmenené (304) stav sa vráti. Celý proces je ilustrovaný:

HttpPut httpPut = nový HttpPut (BASE_URL + "1"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("nezmenený_kurz.xml"); httpPut.setEntity (nový InputStreamEntity (resourceStream)); httpPut.setHeader ("Content-Type", "text / xml"); HttpResponse response = client.execute (httpPut); assertEquals (304, response.getStatusLine (). getStatusCode ());

Kde nezmeneny_kurz.xml je súbor na ceste k triede, ktorý uchováva informácie použité na aktualizáciu. Tu je jeho obsah:

 1 ODPOČINOK s pružinou 

Na poslednej ukážke PUT vykonáme platnú aktualizáciu. Nasledujúci obsah je obsah changed_course.xml súbor, ktorého obsah sa používa na aktualizáciu a Samozrejme inštancia v prostriedku webovej služby:

 2 Podpora Apache CXF pre RESTful 

Takto je požiadavka zostavená a vykonaná:

HttpPut httpPut = nový HttpPut (BASE_URL + "2"); InputStream resourceStream = this.getClass (). GetClassLoader () .getResourceAsStream ("changed_course.xml"); httpPut.setEntity (nový InputStreamEntity (resourceStream)); httpPut.setHeader ("Content-Type", "text / xml");

Poďme validovať a PUT požiadavka na server a overenie úspešného načítania:

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

Poďme overiť nové stavy prostriedku webovej služby:

Kurz kurzu = getCourse (2); assertEquals (2, course.getId ()); assertEquals ("Podpora Apache CXF pre RESTful", course.getName ());

Nasledujúci úryvok kódu zobrazuje obsah odpovede XML pri požiadavke GET na predtým nahrané Samozrejme objekt je odoslaný:

  2 Podpora Apache CXF pre RESTful 

5.5. ODSTRÁNIŤ Žiadosti

Najskôr sa pokúsme odstrániť neexistujúci Študent inštancia. Operácia by mala zlyhať a zodpovedajúca odpoveď by mala byť Nenájdené (404) očakáva sa stav:

HttpDelete httpDelete = nový HttpDelete (BASE_URL + "1 / students / 3"); HttpResponse response = client.execute (httpDelete); assertEquals (404, response.getStatusLine (). getStatusCode ());

V druhom testovacom prípade pre ODSTRÁNIŤ žiadosti, vytvoríme, vykonáme a overíme žiadosť:

HttpDelete httpDelete = nový HttpDelete (BASE_URL + "1 / students / 1"); HttpResponse response = client.execute (httpDelete); assertEquals (200, response.getStatusLine (). getStatusCode ());

Nové stavy prostriedku webovej služby overujeme pomocou nasledujúceho útržku kódu:

Kurz kurzu = getCourse (1); assertEquals (1, course.getStudents (). size ()); assertEquals (2, course.getStudents (). get (0) .getId ()); assertEquals ("Študent B", course.getStudents (). get (0) .getName ());

Ďalej uvádzame odpoveď XML, ktorá je prijatá po žiadosti o prvú Samozrejme objekt v prostriedku webovej služby:

  1 ODPOČINOK so študentom jari 2 B 

Je jasné, že prvý Študent bol úspešne odstránený.

6. Vykonanie testu

V časti 4 bolo popísané, ako vytvoriť a zničiť a Server napríklad v hlavný metóda RestfulServer trieda.

Posledným krokom k uvedeniu servera do prevádzky je jeho vyvolanie hlavný metóda. Aby sa to dosiahlo, je doplnok Exec Maven zahrnutý a nakonfigurovaný v súbore Maven POM:

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

Najnovšiu verziu tohto pluginu nájdete prostredníctvom tohto odkazu.

V procese kompilácie a zabalenia artefaktu ilustrovaného v tomto výučbe plugin Maven Surefire automaticky vykoná všetky testy uzavreté v triedach s názvami začínajúcimi alebo končiacimi Test. V takom prípade by mal byť doplnok nakonfigurovaný tak, aby vylučoval tieto testy:

 maven-surefire-plugin 2.19.1 ** / ServiceTest 

S vyššie uvedenou konfiguráciou ServiceTest je vylúčené, pretože ide o názov testovacej triedy. Môžete si zvoliť ľubovoľný názov pre túto triedu za predpokladu, že testy v ňom obsiahnuté nespustia plugin Maven Surefire skôr, ako je server pripravený na pripojenie.

Najnovšiu verziu doplnku Maven Surefire nájdete tu.

Teraz môžete vykonať exec: java cieľom je spustiť server webových služieb RESTful a potom spustiť vyššie uvedené testy pomocou IDE. Rovnako môžete spustiť test vykonaním príkazu mvn -Dtest = ServiceTest test v termináli.

7. Záver

Tento výukový program ilustroval použitie Apache CXF ako implementácie JAX-RS. Ukázalo sa, ako by sa rámec mohol použiť na definovanie zdrojov pre webovú službu RESTful a na vytvorenie servera na publikovanie služby.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte GitHub.