Hiba a REST kezelésekor rugóval

REST felső

Most jelentettem be az újat Tanulj tavaszt tanfolyam, amelynek középpontjában az 5. tavasz és a tavaszi bakancs 2 alapjai állnak:

>> ELLENŐRIZZE A FOLYAMATOT

1. Áttekintés

Ez az oktatóanyag szemlélteti hogyan lehet megvalósítani a kivételek kezelését a Spring segítségével a REST API számára. Kapunk egy kis történelmi áttekintést is, és megnézzük, hogy a különböző verziók mely új lehetőségeket vezették be.

A 3.2 tavasz előtt a kivételek két fő megközelítése volt a Spring MVC alkalmazásban HandlerExceptionResolver vagy a @ExceptionHandler annotáció. Mindkettőnek vannak egyértelmű hátrányai.

3,2 óta nálunk van @ControllerAdvice annotáció az előző két megoldás korlátainak kezelése és az egységes kivételkezelés előmozdítása az egész alkalmazásban.

Most Az 5. tavasz bemutatja a ResponseStatusException osztály - gyors módszer az alapvető hibakezelésre a REST API-jainkban.

Mindezeknek van egy közös vonása: foglalkoznak a az aggodalmak elkülönítése nagyon jól. Az alkalmazás általában kivételt vethet valamilyen hiba jelzésére, amelyet aztán külön kezelnek.

Végül meglátjuk, mit hoz a Spring Boot az asztalra, és hogyan konfigurálhatjuk igényeinknek megfelelően.

2. 1. megoldás: Vezérlőszint @ExceptionHandler

Az első megoldás a @Vezérlő szint. Meghatározunk egy módszert a kivételek kezelésére, és ezt feljegyezzük @ExceptionHandler:

public class FooController {// ... @ExceptionHandler ({CustomException1.class, CustomException2.class}) public void handleException () {//}}

Ennek a megközelítésnek van egy fő hátránya: Tő @ExceptionHandler az annotált módszer csak az adott Vezérlőnél aktív, nem globálisan az egész alkalmazásra. Természetesen, ha ezt hozzáadjuk minden vezérlőhöz, az nem lesz megfelelő egy általános kivételkezelő mechanizmushoz.

Megoldhatjuk ezt a korlátozást azzal, hogy az összes vezérlő kiterjeszti az alapvezérlő osztályt.

Ez a megoldás azonban problémát jelenthet olyan alkalmazásoknál, ahol ez bármilyen okból nem lehetséges. Például a Vezérlők már kiterjeszthetnek egy másik alaposztályt, amely lehet egy másik edényben, vagy nem közvetlenül módosítható, vagy maguk nem közvetlenül módosíthatók.

Ezután megvizsgáljuk a kivételkezelési probléma megoldásának egy másik módját - egy globális megoldást, amely nem tartalmaz semmilyen változást a meglévő tárgyakra, például a vezérlőkre.

3. 2. megoldás: a HandlerExceptionResolver

A második megoldás egy an definiálása HandlerExceptionResolver. Ez megoldja az alkalmazás által felvetett esetleges kivételeket. Ez lehetővé teszi számunkra a egységes kivételkezelő mechanizmus a REST API-ban.

Mielőtt egyéni megoldót választana, nézzük át a meglévő megvalósításokat.

3.1. ExceptionHandlerExceptionResolver

Ezt a megoldót a 3.1 tavasszal vezették be, és alapértelmezés szerint engedélyezve van a DispatcherServlet. Ez tulajdonképpen annak a fő alkotóeleme, hogy a @ExceptionHandler mechanizmus bemutatta a korábbi műveket.

3.2. DefaultHandlerExceptionResolver

Ezt a megoldót a 3.0 tavasszal vezették be, és alapértelmezés szerint engedélyezve van a DispatcherServlet.

Arra szolgál, hogy megoldja a szokásos tavaszi kivételeket a hozzájuk tartozó HTTP állapotkódokról, nevezetesen az Ügyfél hiba 4xx és szerver hiba 5xx állapotkódok. Itt van a teljes lista a tavaszi kivételek közül, és hogyan viszonyulnak az állapotkódokhoz.

Míg a válasz állapotkódját megfelelően állítja be, egyet korlátozás az, hogy nem állít semmit a Válasz testére. És a REST API-hoz - az Állapotkód valóban nem elegendő információ az Ügyfél számára történő bemutatáshoz - a válasznak tartalmaznia kell egy törzset is, hogy az alkalmazás további információkat adhasson a hibáról.

Ez megoldható a nézet felbontásának konfigurálásával és a hibatartalom renderelésével ModelAndView, de a megoldás nyilvánvalóan nem optimális. Ezért a 3.2 tavasz bevezetett egy jobb lehetőséget, amelyet egy későbbi részben tárgyalunk.

3.3. ResponseStatusExceptionResolver

Ezt a megoldót a 3.0 tavasz is bemutatta, és alapértelmezés szerint engedélyezve van a DispatcherServlet.

Fő feladata a @ResponseStatus az egyedi kivételeknél elérhető kommentár és ezeknek a kivételeknek a HTTP állapotkódokhoz való hozzárendelése.

Egy ilyen egyedi kivétel a következőképpen nézhet ki:

@ResponseStatus (érték = HttpStatus.NOT_FOUND) nyilvános osztály A MyResourceNotFoundException kiterjeszti a RuntimeException {public MyResourceNotFoundException () {super (); } public MyResourceNotFoundException (karakterlánc üzenet, dobható ok) {szuper (üzenet, ok); } public MyResourceNotFoundException (String üzenet) {szuper (üzenet); } public MyResourceNotFoundException (dobható ok) {szuper (ok); }}

Ugyanaz, mint a DefaultHandlerExceptionResolver, ez a felbontó korlátozott módon kezeli a válasz törzsét - feltérképezi az állapotkódot a válaszon, de a test még mindig nulla.

3.4. SimpleMappingExceptionResolver és AnnotationMethodHandlerExceptionResolver

A SimpleMappingExceptionResolver már jó ideje létezik. A régebbi Spring MVC modellből jön ki, és az nem nagyon releváns a REST szolgáltatás szempontjából. Alapvetően a kivétel osztálynevek feltérképezésére használjuk a nevek megtekintéséhez.

A AnnotationMethodHandlerExceptionResolver 3.0 tavaszán vezették be, hogy kezelje a kivételeket a @ExceptionHandler megjegyzéssel, de a ExceptionHandlerExceptionResolver a 3.2 tavasztól.

3.5. Egyedi HandlerExceptionResolver

A kombináció DefaultHandlerExceptionResolver és ResponseStatusExceptionResolver hosszú utat mutat be a tavaszi RESTful szolgáltatás jó hibakezelési mechanizmusának biztosítása felé. A hátránya, amint azt korábban említettük, nincs kontroll a válasz teste felett.

Ideális esetben szeretnénk, ha JSON vagy XML is kiadható lenne, attól függően, hogy az ügyfél milyen formátumot kért (a Elfogad fejléc).

Önmagában ez indokolja az alkotást egy új, egyedi kivétel-megoldó:

A @Component public class RestResponseStatusExceptionResolver kiterjeszti az AbstractHandlerExceptionResolver {@Override védett ModelAndView doResolveException (HttpServletRequest kérés, HttpServletResponse válasz, Objektumkezelő, Kivétel ex) {try {if (ex instanceof Illegle; } ...} catch (Exception handlerException) {logger.warn ("A [" + ex.getClass (). getName () + "] kezelése Kivételt eredményezett", handlerException); } return null; } privát ModelAndView handleIllegalArgument (IllegalArgumentException ex, HttpServletResponse válasz) dobja az IOException {response.sendError (HttpServletResponse.SC_CONFLICT); Karakterlánc elfogadása = request.getHeader (HttpHeaders.ACCEPT); ... return new ModelAndView (); }}

Az egyik részlet, amelyet itt észre kell venni, hogy hozzáférünk a kérés önmagában, tehát figyelembe vehetjük a Elfogad az ügyfél által küldött fejléc.

Például, ha az ügyfél kéri alkalmazás / json, akkor hibaállapot esetén meg kell győződnünk arról, hogy egy válasz kódot adunk vissza kódolva alkalmazás / json.

A másik fontos megvalósítási részlet az visszatérünk a ModelAndView - ez a válasz teste, és ez lehetővé teszi számunkra, hogy minden szükségeset beállítsunk rajta.

Ez a megközelítés következetes és könnyen konfigurálható mechanizmus a Spring REST szolgáltatás hibakezeléséhez.

Ennek azonban vannak korlátai: kölcsönhatásba lép az alacsony szinttel HtttpServletResponse és illeszkedik a használt MVC régi modellbe ModelAndView, így van még mit javítani.

4. 3. megoldás: @ControllerAdvice

A 3.2 tavasz támogatja a egy globális @ExceptionHandler a ... val @ControllerAdvice annotáció.

Ez lehetővé teszi egy olyan mechanizmust, amely elszakad a régebbi MVC modelltől és kihasználja azt ResponseEntity a típus biztonságával és rugalmasságával együtt @ExceptionHandler:

A @ControllerAdvice nyilvános osztály RestResponseEntityExceptionHandler kiterjeszti a ResponseEntityExceptionHandler {@ExceptionHandler (érték = {IllegalArgumentException.class, IllegalStateException.class}) védett ResponseEntity handleConflict = RuntimeEx; return handleExceptionInternal (ex, bodyOfResponse, új HttpHeaders (), HttpStatus.CONFLICT, kérés); }}

A@ControllerAdvice az annotáció lehetővé teszi számunkra, hogy megszilárdítsa a többszörös, szétszórt @ExceptionHandlers elejétől egyetlen, globális hibakezelő komponenssé.

A tényleges mechanizmus rendkívül egyszerű, de nagyon rugalmas is:

  • Ez teljes kontrollt biztosít számunkra a válasz törzse, valamint az állapotkód felett.
  • Számos kivétel feltérképezését biztosítja ugyanarról a módszerről, amelyeket együtt kell kezelni.
  • Jól kihasználja az újabb RESTful-t ResposeEntity válasz.

Itt egy dolgot érdemes szem előtt tartani egyezzen a deklarált kivételekkel @ExceptionHandler a módszer argumentumaként használt kivétel.

Ha ezek nem egyeznek, a fordító nem panaszkodik - nincs oka annak - és Spring sem panaszkodik.

Amikor azonban a kivétel ténylegesen futásidőben történik, a kivételmegoldó mechanizmus meghiúsul:

java.lang.IllegalStateException: Nincs megfelelő megoldó az argumentumhoz [0] [type = ...] HandlerMethod részletek: ...

5. 4. megoldás: ResponseStatusException (5. és feletti tavasz)

Az 5. tavasz bevezette a ResponseStatusException osztály.

Hozhatunk létre egy példányt, amely egy HttpStatus és opcionálisan a ok és a ok:

@GetMapping (value = "/ {id}") public Foo findById (@PathVariable ("id") Long id, HttpServletResponse response) {try {Foo resourceById = RestPreconditions.checkFound (service.findOne (id)); eventPublisher.publishEvent (új SingleResourceRetrievedEvent (ez, válasz)); return resourceById; } catch (MyResourceNotFoundException exc) {dobjon új ResponseStatusException-t (HttpStatus.NOT_FOUND, "Foo Not Found", exc); }}

Milyen előnyei vannak a használatnak ResponseStatusException?

  • Kiváló prototípus-készítéshez: Egy alapmegoldást elég gyorsan megvalósíthatunk.
  • Egy típus, több állapotkód: Egy kivételtípus több különböző válaszhoz vezethet. Ez csökkenti a szoros kapcsolást a @ExceptionHandler.
  • Nem kell annyi egyedi kivételosztályt létrehozni.
  • Nekünk van nagyobb ellenőrzés a kivételkezelés felett mivel a kivételek programszerűen hozhatók létre.

És mi van a kompromisszumokkal?

  • A kivételkezelésnek nincs egységes módja: Nehezebb bizonyos alkalmazásszintű egyezményeket érvényre juttatni, szemben azokkal @ControllerAdvice, amely globális megközelítést kínál.
  • Kódmásolás: Előfordulhat, hogy több vezérlőben replikáljuk a kódot.

Azt is meg kell jegyeznünk, hogy lehetséges a különböző megközelítések kombinálása egy alkalmazáson belül.

Például megvalósíthatjuk a @ControllerAdvice globálisan, de szintén ResponseStatusExceptions helyben.

Óvatosnak kell lennünk: Ha ugyanazt a kivételt többféleképpen is kezelhetjük, meglepő viselkedést tapasztalhatunk. Lehetséges egyezmény az, hogy egy bizonyos típusú kivételeket mindig egyetlen módon kezelnek.

További részletekért és további példákért olvassa el a bemutatónkat ResponseStatusException.

6. Kezelje a Tavaszi Biztonságban megtagadott hozzáférést

A hozzáférés megtagadva akkor fordul elő, amikor egy hitelesített felhasználó olyan erőforrásokhoz próbál hozzáférni, amelyekhez nincs elegendő jogosultsága.

6.1. MVC - Egyéni hiba oldal

Először nézzük meg a megoldás MVC stílusát, és nézzük meg, hogyan lehet testreszabni egy hibaoldalt a hozzáférés megtagadva számára.

Az XML konfiguráció:

  ...  

És a Java konfiguráció:

A @Orride protected void configure (HttpSecurity http) dobja a (z) {http.authorizeRequests () .antMatchers ("/ admin / *") kivételt. HasAnyRole ("ROLE_ADMIN") ... .és () .exceptionHandling (). AccessDeniedPage ("/ my-error-page "); }

Amikor a felhasználók elegendő jogosultság nélkül próbálnak hozzáférni egy erőforráshoz, akkor a rendszer átirányítja őket “/ My-error-page”.

6.2. Egyedi AccessDeniedHandler

Ezután nézzük meg, hogyan írjuk meg a szokásainkat AccessDeniedHandler:

@Component public class CustomAccessDeniedHandler implementates AccessDeniedHandler {@Override public void hand (HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) IOException, ServletException {response.sendRedirect ("/ my my; }}

Most pedig konfiguráljuk a használatával XML konfiguráció:

  ...  

0r Java konfigurációval:

@Autowired privát CustomAccessDeniedHandler accessDeniedHandler; A @Orride protected void configure (HttpSecurity http) a {http.authorizeRequests () .antMatchers ("/ admin / *") kivételt dobja. HasAnyRole ("ROLE_ADMIN") ... .és () .exceptionHandling (). AccessDeniedHandler (accessDeniedHandler) }

Vegye figyelembe, hogy a mi CustomAccessDeniedHandler, testreszabhatjuk a választ tetszés szerint egy egyedi hibaüzenet átirányításával vagy megjelenítésével.

6.3. REST és módszer szintű biztonság

Végül nézzük meg, hogyan kell kezelni a módszer szintű biztonságot @PreAuthorize, @PostAuthorize, és @ Biztonságos Hozzáférés megtagadva.

Természetesen a globális kivételkezelő mechanizmust fogjuk használni, amelyet korábban megbeszéltünk AccessDeniedException is:

A @ControllerAdvice nyilvános osztály RestResponseEntityExceptionHandler kiterjeszti a ResponseEntityExceptionHandler {@ExceptionHandler ({AccessDeniedException.class}) nyilvános ResponseEntity handleAccessDeniedException (Exception ex, WebRequest kérés) Http "URL" } ...}

7. Tavaszi csomagtartó támogatás

A Spring Boot egy ErrorController megvalósítás a hibák ésszerű módon történő kezelése érdekében.

Dióhéjban egy tartalék hiba oldalt szolgál a böngészők számára (más néven a Whitelabel Error Page), és egy JSON választ a RESTful, nem HTML kérésekhez:

{"timestamp": "2019-01-17T16: 12: 45.977 + 0000", "status": 500, "error": "Belső szerverhiba", "message": "Hiba a kérelem feldolgozásában!", "path" : "/ saját-végpont-kivétellel"}

Szokás szerint a Spring Boot lehetővé teszi ezen funkciók tulajdonságokkal történő konfigurálását:

  • server.error.whitelabel.enabled: a Whitelabel Error Page letiltására használható, és a szervlet-tárolóra hagyatkozhat HTML hibaüzenet küldésében
  • server.error.include-stacktrace: egy valamivel mindig érték; tartalmazza a stacktrace-t mind a HTML-ben, mind a JSON alapértelmezett válaszában

Ezen tulajdonságokon kívül megadhatjuk saját nézetmegoldó térképünket a /hiba, felülírja a Whitelabel oldalt.

Testreszabhatjuk azokat az attribútumokat is, amelyeket meg akarunk jeleníteni a válaszban, az an beillesztésével ErrorAttributes bab összefüggésben. Meghosszabbíthatjuk a DefaultErrorAttributes osztály a Spring Boot nyújtotta a dolgok megkönnyítése érdekében:

@Component public class MyCustomErrorAttributes extends DefaultErrorAttributes {@Orride public Map getErrorAttributes (WebRequest webRequest, boolean includeStackTrace) {Map errorAttributes = super.getErrorAttributes (webRequest, includeStackTrace); errorAttributes.put ("locale", webRequest.getLocale () .toString ()); errorAttributes.remove ("hiba"); // ... return errorAttributes; }}

Ha tovább akarunk menni, és meg akarjuk határozni (vagy felülbíráljuk), hogyan kezeli az alkalmazás egy adott tartalomtípus hibáit, regisztrálhatunk egy ErrorController bab.

Ismét használhatjuk az alapértelmezett értéket BasicErrorController a Spring Boot nyújtotta segítségünkre.

Képzeljük el például, hogy testre szeretnénk szabni, hogyan kezeli alkalmazásunk az XML-végpontokban kiváltott hibákat. Csak annyit kell tennünk, hogy definiálunk egy nyilvános módszert a @RequestMapping, és annak kimondása produkálja application / xml média típus:

@Component public class MyErrorController kiterjeszti a BasicErrorController {public MyErrorController (ErrorAttributes errorAttributes) {super (errorAttributes, new ErrorProperties ()); } @RequestMapping (termel = MediaType.APPLICATION_XML_VALUE) nyilvános ResponseEntity xmlError (HttpServletRequest kérés) {// ...}}

8. Következtetés

Ez a cikk a REST API kivételkezelési mechanizmusának tavasszal történő megvalósításának számos módját tárgyalja, kezdve a régebbi mechanizmussal és folytatva a Spring 3.2 támogatással, valamint a 4.x és 5.x verziókkal.

Mint mindig, a cikkben bemutatott kód elérhető a GitHubon.

A Spring Security-hez kapcsolódó kódért ellenőrizheti a spring-security-rest modult.

REST alsó

Most jelentettem be az újat Tanulj tavaszt tanfolyam, amelynek középpontjában az 5. tavasz és a tavaszi bakancs 2 alapjai állnak:

>> ELLENŐRIZZE A FOLYAMATOT