Spracovanie chyby pre REST s pružinou

ODPOČINOK Najlepšie

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

1. Prehľad

Tento návod bude ilustrovať ako implementovať spracovanie výnimiek pomocou pružiny pre REST API. Získame tiež trochu historického prehľadu a uvidíme, ktoré nové možnosti jednotlivé verzie predstavili.

Pred jarom 3.2 boli dva hlavné prístupy k spracovaniu výnimiek v jarnej aplikácii MVC HandlerExceptionResolver alebo @ExceptionHandler anotácia. Obidve majú niektoré nevýhody.

Od 3.2 máme @ControllerAdvice anotácia na riešenie obmedzení predchádzajúcich dvoch riešení a na podporu jednotného spracovania výnimiek v celej aplikácii.

Teraz Jar 5 predstavuje ResponseStatusException trieda - rýchly spôsob základného spracovania chýb v našich rozhraniach REST API.

Všetky majú jednu spoločnú vec: Zaoberajú sa oddelenie obáv veľmi dobre. Aplikácia môže bežne hádzať výnimky, aby naznačila zlyhanie nejakého druhu, ktoré sa potom bude riešiť osobitne.

Na záver uvidíme, čo Spring Boot prinesie na stôl a ako ho môžeme nakonfigurovať tak, aby vyhovoval našim potrebám.

2. Riešenie 1: Úroveň ovládača @ExceptionHandler

Prvé riešenie funguje na serveri @ Kontrolór úrovni. Zadefinujeme metódu na spracovanie výnimiek a anotujeme ju pomocou @ExceptionHandler:

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

Tento prístup má veľkú nevýhodu: Ton @ExceptionHandler komentovaná metóda je aktívna iba pre konkrétny radič, nie globálne pre celú aplikáciu. Jeho pridanie do každého radiča samozrejme nie je vhodné pre všeobecný mechanizmus spracovania výnimiek.

Toto obmedzenie môžeme obísť tým, že budeme mať všetky radiče rozširujú triedu základných radičov.

Toto riešenie však môže robiť problém aplikáciám, kde to z akýchkoľvek dôvodov nie je možné. Napríklad ovládače už môžu vychádzať z inej základnej triedy, ktorá môže byť v inej nádobe alebo ju nemožno priamo meniť, alebo sama nemusí byť priamo upraviteľná.

Ďalej sa pozrieme na ďalší spôsob riešenia problému so spracovaním výnimiek - globálny a nezahŕňajúci žiadne zmeny v existujúcich artefaktoch, ako sú napríklad radiče.

3. Riešenie 2: HandlerExceptionResolver

Druhým riešením je definovať HandlerExceptionResolver. To vyrieši každú výnimku vyvolanú aplikáciou. Umožní nám to tiež implementovať a jednotný mechanizmus vybavovania výnimiek v našom REST API.

Predtým, ako sa pustíme do vlastného prekladača, prejdime si existujúce implementácie.

3.1. ExceptionHandlerExceptionResolver

Tento prekladač bol predstavený na jar 3.1 a je predvolene povolený v DispatcherServlet. Toto je vlastne kľúčová súčasť toho, ako @ExceptionHandler mechanizmus predstavený skôr.

3.2. DefaultHandlerExceptionResolver

Tento prekladač bol predstavený na jar 3.0 a je predvolene povolený v DispatcherServlet.

Používa sa na riešenie štandardných jarných výnimiek z ich príslušných stavových kódov HTTP, konkrétne chyby klienta 4xx a chyba servera 5xx stavové kódy. Tu je úplný zoznam o jarných výnimkách, ktoré spracováva, a o tom, ako sa mapujú na stavové kódy.

Aj keď správne nastavuje stavový kód odpovede, jeden obmedzením je, že neurčuje nič do tela odpovede. A pre REST API - stavový kód skutočne nie je dostatočný údaj, ktorý je potrebné predložiť klientovi - odpoveď musí mať tiež telo, ktoré aplikácii umožní poskytnúť ďalšie informácie o zlyhaní.

To je možné vyriešiť konfiguráciou rozlíšenia zobrazenia a vykreslením chybového obsahu ModelAndView, ale riešenie zjavne nie je optimálne. Preto jar 3.2 predstavila lepšiu možnosť, o ktorej si povieme v ďalšej časti.

3.3. ResponseStatusExceptionResolver

Tento prekladač bol tiež predstavený na jar 3.0 a je predvolene povolený v DispatcherServlet.

Jeho hlavnou zodpovednosťou je používať @ResponseStatus anotácia k dispozícii pre vlastné výnimky a na mapovanie týchto výnimiek na stavové kódy HTTP.

Takáto vlastná výnimka môže vyzerať takto:

@ResponseStatus (hodnota = HttpStatus.NOT_FOUND) verejná trieda MyResourceNotFoundException rozširuje RuntimeException {public MyResourceNotFoundException () {super (); } public MyResourceNotFoundException (reťazcová správa, hoditeľná príčina) {super (správa, príčina); } public MyResourceNotFoundException (reťazcová správa) {super (správa); } public MyResourceNotFoundException (hoditeľná príčina) {super (príčina); }}

To isté ako DefaultHandlerExceptionResolver, tento rezolver je obmedzený spôsobom, ktorý narába s telom odpovede - mapuje stavový kód na reakcii, ale telo je stále nulový.

3.4. SimpleMappingExceptionResolver a AnnotationMethodHandlerExceptionResolver

The SimpleMappingExceptionResolver existuje už dosť dlho. Vychádza zo staršieho modelu Spring MVC a je nie je pre službu REST veľmi dôležitá. V zásade ho používame na mapovanie názvov tried výnimiek na ich prezeranie.

The AnnotationMethodHandlerExceptionResolver bol predstavený na jar 3.0 na riešenie výnimiek prostredníctvom @ExceptionHandler anotácia, ale jeho podpora bola ukončená ExceptionHandlerExceptionResolver od jari 3.2.

3.5. Vlastné HandlerExceptionResolver

Kombinácia DefaultHandlerExceptionResolver a ResponseStatusExceptionResolver ide dlhou cestou k zabezpečeniu dobrého mechanizmu spracovania chýb pre službu Spring RESTful. Nevýhodou je, ako už bolo spomenuté, žiadna kontrola nad telom odpovede.

V ideálnom prípade by sme chceli mať možnosť výstupu buď JSON alebo XML, v závislosti od toho, aký formát si klient vyžiadal (prostredníctvom súhlasiť hlavička).

Toto samo osebe oprávňuje vytvárať nový, vlastný prekladač výnimiek:

@Component public class RestResponseStatusExceptionResolver rozširuje AbstractHandlerExceptionResolver {@Override chránený ModelAndView doResolveException (požiadavka HttpServletRequest, odpoveď HttpServletResponse, obsluha objektov, výnimka z výnimky) {try {if (ex instanceof IllegalArgumentException) {return) } ...} catch (Exception handlerException) {logger.warn ("Výsledkom manipulácie s [" + ex.getClass (). getName () + "] bola výnimka", handlerException); } return null; } private ModelAndView handleIllegalArgument (IllegalArgumentException ex, HttpServletResponse response) hodí IOException {response.sendError (HttpServletResponse.SC_CONFLICT); Reťazec accept = request.getHeader (HttpHeaders.ACCEPT); ... vrátiť nový ModelAndView (); }}

Tu si musíme všimnúť jeden detail, že máme prístup k žiadosť sám o sebe, takže môžeme zvážiť hodnotu súhlasiť hlavička zaslaná klientom.

Napríklad ak o to klient požiada aplikácia / json, potom by sme sa v prípade chybového stavu chceli ubezpečiť, že vrátime telo odpovede zakódované pomocou aplikácia / json.

Ďalším dôležitým detailom implementácie je to vraciame a ModelAndView - toto je telo odpovede, a umožní nám to nastaviť na ňu všetko, čo je potrebné.

Tento prístup je konzistentný a ľahko konfigurovateľný mechanizmus na spracovanie chýb služby Spring REST.

Má však určité obmedzenia: je to interakcia s nízkou úrovňou HtttpServletResponse a zapadá do starého modelu MVC, ktorý používa ModelAndView, takže stále je čo zlepšovať.

4. Riešenie 3: @ControllerAdvice

Jar 3.2 prináša podporu pre globálny @ExceptionHandler s @ControllerAdvice anotácia.

To umožňuje mechanizmus, ktorý sa oddeľuje od staršieho modelu MVC a využíva ho ResponseEntity spolu s typom bezpečnosti a flexibility @ExceptionHandler:

@ControllerAdvice verejná trieda RestResponseEntityExceptionHandler rozširuje ResponseEntityExceptionHandler {@ExceptionHandler (value = {IllegalArgumentException.class, IllegalStateException.class}) chránený ResponseEntity handleConflict (RuntimeException ex, žiadosť WebRequest) return handleExceptionInternal (ex, bodyOfResponse, new HttpHeaders (), HttpStatus.CONFLICT, request); }}

The@ControllerAdvice anotácia nám umožňuje konsolidovať naše viacnásobné, rozptýlené @ExceptionHandlers predtým do jedného globálneho komponentu na spracovanie chýb.

Samotný mechanizmus je mimoriadne jednoduchý, ale zároveň veľmi flexibilný:

  • Dáva nám úplnú kontrolu nad telom odpovede, ako aj nad stavovým kódom.
  • Poskytuje mapovanie niekoľkých výnimiek z tej istej metódy, ktoré sa majú spracovať spoločne.
  • Dobre využíva novšiu verziu RESTful ResposeEntity odpoveď.

Tu je treba mať na pamäti jednu vec zodpovedať výnimkám deklarovaným s @ExceptionHandler na výnimku použitú ako argument metódy.

Ak sa tieto nezhodujú, kompilátor sa nebude sťažovať - ​​žiadny dôvod, prečo by mal - a Spring sa tiež nebude sťažovať.

Keď je však výnimka skutočne vyvolaná za behu, mechanizmus riešenia výnimiek zlyhá s:

java.lang.IllegalStateException: Nie je vhodný prekladač pre argument [0] [typ = ...] Podrobnosti HandlerMethod: ...

5. Riešenie 4: ResponseStatusException (Jar 5 a vyššie)

Jar 5 predstavila ResponseStatusException trieda.

Môžeme vytvoriť jeho inštanciu poskytujúcu HttpStatus a voliteľne a dôvod a a príčina:

@GetMapping (value = "/ {id}") public Foo findById (@PathVariable ("id") Long id, HttpServletResponse response) {try {Foo resourceById = RestPreconditions.checkFound (service.findOne (id)); eventPublisher.publishEvent (nový SingleResourceRetrievedEvent (toto, odpoveď)); vrátiť resourceById; } catch (MyResourceNotFoundException exc) {throw new ResponseStatusException (HttpStatus.NOT_FOUND, "Foo Not Found", exc); }}

Aké sú výhody používania ResponseStatusException?

  • Vynikajúce pre prototypy: Základné riešenie dokážeme implementovať pomerne rýchlo.
  • Jeden typ, viac stavových kódov: Jeden typ výnimky môže viesť k viacerým rôznym odpovediam. To znižuje tesné spojenie v porovnaní s @ExceptionHandler.
  • Nebudeme musieť vytvárať toľko vlastných tried výnimiek.
  • Máme väčšiu kontrolu nad spracovaním výnimiek pretože výnimky je možné vytvárať programovo.

A čo kompromisy?

  • Neexistuje jednotný spôsob spracovania výnimiek: Na rozdiel od je ťažšie presadiť niektoré konvencie pre celú aplikáciu @ControllerAdvice, ktorý poskytuje globálny prístup.
  • Duplikácia kódu: Môžeme sa ocitnúť pri replikácii kódu vo viacerých radičoch.

Mali by sme tiež poznamenať, že je možné kombinovať rôzne prístupy v rámci jednej aplikácie.

Napríklad môžeme implementovať a @ControllerAdvice globálne, ale aj ResponseStatusExceptions lokálne.

Musíme však byť opatrní: Ak sa dá rovnaká výnimka vyriešiť viacerými spôsobmi, môžeme si všimnúť prekvapivé správanie. Možnou konvenciou je zaobchádzať s jedným konkrétnym druhom výnimky vždy jedným spôsobom.

Viac podrobností a ďalších príkladov nájdete v našom výučbe na ResponseStatusException.

6. Zaobchádzajte s prístupom odmietnutým v rámci jarnej bezpečnosti

Prístup odmietnutý nastane, keď sa autentifikovaný užívateľ pokúsi získať prístup k prostriedkom, ku ktorým nemá dostatočné oprávnenie.

6.1. MVC - vlastná chybová stránka

Najskôr sa pozrime na štýl riešenia MVC a zistíme, ako prispôsobiť chybovú stránku pre prístup odmietnutý.

Konfigurácia XML:

  ...  

A konfigurácia Java:

@Override protected void configure (HttpSecurity http) vyvolá výnimku {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... .and () .exceptionHandling (). AccessDeniedPage ("/ moja-chybova-stranka "); }

Keď sa používatelia pokúsia získať prístup k prostriedku bez dostatočného oprávnenia, budú presmerovaní na adresu „/ Moja-chybova-stranka“.

6.2. Vlastné AccessDeniedHandler

Ďalej sa pozrime, ako napísať náš zvyk AccessDeniedHandler:

@Component public class CustomAccessDeniedHandler implementuje AccessDeniedHandler {@Override public void handle (požiadavka HttpServletRequest, HttpServletResponse odpoveď, AccessDeniedException ex) hodí IOException, ServletException {response.sendRedirect ("/ my-error-page"); }}

A teraz to nakonfigurujme pomocou Konfigurácia XML:

  ...  

0r pomocou konfigurácie Java:

@Autowired private CustomAccessDeniedHandler accessDeniedHandler; @Override protected void configure (HttpSecurity http) vyvolá výnimku {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... .and () .exceptionHandling (). AccessDeniedHandler (accessDeniedHandler) }

Všimnite si, ako v našom CustomAccessDeniedHandler, môžeme odpoveď prispôsobiť podľa želania presmerovaním alebo zobrazením vlastnej chybovej správy.

6.3. ODPOČINOK a zabezpečenie na úrovni metód

Na záver sa pozrime, ako zaobchádzať so zabezpečením na úrovni metódy @PreAuthorize, @PostAuthorizea @Zabezpečiť Prístup zamietnutý.

Samozrejme, na riešenie problému použijeme globálny mechanizmus spracovania výnimiek, o ktorom sme už hovorili AccessDeniedException tiež:

@ControllerAdvice veřejná trieda RestResponseEntityExceptionHandler rozširuje ResponseEntityExceptionHandler {@ExceptionHandler ({AccessDeniedException.class}) public ResponseEntity handleAccessDeniedException (Exception ex, WebRequest request) {return new ResponseEntity ("Access denied message here", nový prístup odmietnutý) " } ...}

7. Podpora Spring Boot

Spring Boot poskytuje ErrorController implementácia na zvládnutie chýb rozumným spôsobom.

Stručne povedané, poskytuje záložnú chybovú stránku pre prehliadače (tiež známa ako chybová stránka Whitelabel) a odpoveď JSON pre RESTful požiadavky iné ako HTML:

{"timestamp": "2019-01-17T16: 12: 45.977 + 0000", "status": 500, "error": "Interná chyba servera", "správa": "Chyba pri spracovaní požiadavky!", "cesta" : "/ môj-koncový bod-s-výnimkami"}

Ako obvykle, Spring Boot umožňuje konfiguráciu týchto funkcií s vlastnosťami:

  • server.error.whitelabel.enabled: možno použiť na deaktiváciu chybovej stránky Whitelabel a spoliehanie sa na to, že kontajner servletu poskytne chybové hlásenie HTML
  • server.error.include-stacktrace: s vždy hodnota; Zahŕňa stacktrace v predvolenej odpovedi HTML aj JSON

Okrem týchto vlastností môžeme poskytnúť naše vlastné mapovanie na prekladanie pohľadov pre /chyba, potlačenie stránky s bielou značkou.

Atribúty, ktoré chceme zobraziť v odpovedi, môžeme prispôsobiť aj zahrnutím znaku Atribúty chyby fazuľa v kontexte. Môžeme predĺžiť DefaultErrorAttributes trieda poskytovaná programom Spring Boot, aby sa veci uľahčili:

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

Ak chceme ísť ďalej a definovať (alebo prepísať), ako bude aplikácia spracovávať chyby pre konkrétny typ obsahu, môžeme zaregistrovať ErrorController fazuľa.

Môžeme opäť použiť predvolené nastavenie BasicErrorController poskytuje Spring Boot, aby nám pomohli.

Predstavme si napríklad, že chceme prispôsobiť spôsob, akým naša aplikácia spracováva chyby vyvolané v koncových bodoch XML. Všetko, čo musíme urobiť, je definovať verejnú metódu pomocou @RequestMappinga uvedenie, ktoré produkuje prihláška / xml Typ média:

@Component public class MyErrorController rozširuje BasicErrorController {public MyErrorController (ErrorAttributes errorAttributes) {super (errorAttributes, new ErrorProperties ()); } @RequestMapping (vytváří = MediaType.APPLICATION_XML_VALUE) verejná ResponseEntity xmlError (požiadavka HttpServletRequest) {// ...}}

8. Záver

Tento článok pojednával o niekoľkých spôsoboch implementácie mechanizmu spracovania výnimiek pre REST API na jar, počnúc starším mechanizmom a pokračujúcim s podporou Spring 3.2 až po verzie 4.x a 5.x.

Ako vždy, kód uvedený v tomto článku je k dispozícii na GitHub.

Pokiaľ ide o kód súvisiaci s jarnou bezpečnosťou, môžete skontrolovať modul jarná-bezpečnostná-zvyšná časť.

REST spodok

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

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