Feilhåndtering for REST med våren

REST Topp

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

1. Oversikt

Denne opplæringen vil illustrere hvordan du implementerer Exception Handling with Spring for et REST API. Vi får også litt historisk oversikt og se hvilke nye alternativer de forskjellige versjonene introduserte.

Før våren 3.2 var de to viktigste tilnærmingene til å håndtere unntak i en Spring MVC-applikasjon HandlerExceptionResolver eller @ExceptionHandler kommentar. Begge har noen klare ulemper.

Siden 3.2 har vi hatt @ControllerAdvice kommentar for å takle begrensningene i de to foregående løsningene og fremme en enhetlig unntakshåndtering gjennom en hel applikasjon.

Vår 5 introduserer ResponseStatusException klasse - en rask måte for grunnleggende feilhåndtering i våre REST APIer.

Alle disse har en ting til felles: De håndterer separasjon av bekymringer veldig bra. Appen kan kaste unntak normalt for å indikere en feil av noe slag, som deretter håndteres separat.

Til slutt får vi se hva Spring Boot bringer til bordet og hvordan vi kan konfigurere den for å dekke våre behov.

2. Løsning 1: Kontrollenivå @ExceptionHandler

Den første løsningen fungerer på @Kontrollør nivå. Vi vil definere en metode for å håndtere unntak og kommentere den med @ExceptionHandler:

offentlig klasse FooController {// ... @ExceptionHandler ({CustomException1.class, CustomException2.class}) offentlig ugyldig handleException () {//}}

Denne tilnærmingen har en stor ulempe: Than @ExceptionHandler merket metode er bare aktiv for den bestemte kontrolleren, ikke globalt for hele applikasjonen. Å legge dette til hver kontroller gjør det selvfølgelig ikke godt egnet for en generell unntakshåndteringsmekanisme.

Vi kan omgå denne begrensningen ved å ha alle Controllers utvider en Base Controller-klasse.

Imidlertid kan denne løsningen være et problem for applikasjoner der det av en eller annen grunn ikke er mulig. For eksempel kan kontrollerne allerede strekke seg fra en annen baseklasse, som kan være i en annen krukke eller ikke direkte modifiserbar, eller selv ikke kan endres direkte.

Deretter vil vi se på en annen måte å løse unntakshåndteringsproblemet - en som er global og ikke inkluderer noen endringer i eksisterende gjenstander som kontrollere.

3. Løsning 2: HandlerExceptionResolver

Den andre løsningen er å definere en HandlerExceptionResolver. Dette vil løse ethvert unntak som søknaden gir. Det vil også tillate oss å implementere en ensartet mekanisme for unntakshåndtering i vårt REST API.

Før vi går etter en tilpasset resolver, la oss gå gjennom eksisterende implementeringer.

3.1. UnntakHandlerUttakUtløser

Denne resolveren ble introdusert våren 3.1 og er aktivert som standard i DispatcherServlet. Dette er faktisk kjernekomponenten i hvordan @ExceptionHandler mekanisme presentert tidligere arbeider.

3.2. StandardHandlerExceptionResolver

Denne resolveren ble introdusert våren 3.0, og den er aktivert som standard i DispatcherServlet.

Den brukes til å løse standard Spring-unntak fra deres tilsvarende HTTP-statuskoder, nemlig klientfeil 4xx og serverfeil 5xx statuskoder. Her er full liste av våren Unntak det håndterer og hvordan de tilordnes til statuskoder.

Selv om den setter statuskoden for svaret riktig, en begrensningen er at den ikke setter noe på responsen. Og for en REST API - statuskoden er egentlig ikke nok informasjon til å presentere for klienten - svaret må også ha en kropp for å la applikasjonen gi ytterligere informasjon om feilen.

Dette kan løses ved å konfigurere visningsoppløsning og gjengi feilinnhold gjennom ModelAndView, men løsningen er tydeligvis ikke optimal. Derfor introduserte Spring 3.2 et bedre alternativ som vi vil diskutere i et senere avsnitt.

3.3. ResponseStatusExceptionResolver

Denne resolveren ble også introdusert våren 3.0 og er aktivert som standard i DispatcherServlet.

Hovedansvaret er å bruke @ResponseStatus merknader tilgjengelig med tilpassede unntak og for å kartlegge disse unntakene til HTTP-statuskoder.

Et slikt tilpasset unntak kan se ut:

@ResponseStatus (verdi = HttpStatus.NOT_FOUND) offentlig klasse MyResourceNotFoundException utvider RuntimeException {public MyResourceNotFoundException () {super (); } offentlig MyResourceNotFoundException (strengmelding, kastbar årsak) {super (melding, årsak); } offentlig MyResourceNotFoundException (strengmelding) {super (melding); } offentlig MyResourceNotFoundException (kastbar årsak) {super (årsak); }}

Det samme som StandardHandlerExceptionResolver, denne løseren er begrenset i måten den håndterer svaret på - det kartlegger statuskoden på svaret, men kroppen er fortsatt null.

3.4. SimpleMappingExceptionResolver og KommentarMetodeHåndtererExceptionResolver

De SimpleMappingExceptionResolver har eksistert i ganske lang tid. Den kommer ut av den eldre Spring MVC-modellen og er ikke veldig relevant for en REST-tjeneste. Vi bruker det i utgangspunktet til å kartlegge unntaksklassenavn for å vise navn.

De KommentarMetodeHåndtererExceptionResolver ble introdusert våren 3.0 for å håndtere unntak gjennom @ExceptionHandler kommentar, men er utfaset av UnntakHandlerUttakLøser fra og med våren 3.2.

3.5. Tilpasset HandlerExceptionResolver

Kombinasjonen av StandardHandlerExceptionResolver og ResponseStatusExceptionResolver går langt mot å gi en god feilhåndteringsmekanisme for en Spring RESTful Service. Ulempen er, som nevnt tidligere, ingen kontroll over kroppen av responsen.

Ideelt sett ønsker vi å kunne sende ut JSON eller XML, avhengig av hvilket format klienten har bedt om (via Aksepterer Overskrift).

Dette alene rettferdiggjør oppretting en ny, tilpasset unntaksoppløsning:

@Komponent offentlig klasse RestResponseStatusExceptionResolver utvider AbstractHandlerExceptionResolver {@Override-beskyttet ModelAndView doResolveException (HttpServletRequest-forespørsel, HttpServletResponse-respons, Objektbehandler, Exception ex) {prøv {if (ex instanceof IllegalArgal) } ...} catch (Exception handlerException) {logger.warn ("Håndtering av [" + ex.getClass (). getName () + "] resulterte i Exception", handlerException); } returner null; } private ModelAndView handleIllegalArgument (IllegalArgumentException ex, HttpServletResponse response) kaster IOException {respons.sendError (HttpServletResponse.SC_CONFLICT); String accept = request.getHeader (HttpHeaders.ACCEPT); ... returner ny ModelAndView (); }}

En detalj å legge merke til her er at vi har tilgang til be om selve, slik at vi kan vurdere verdien av Aksepterer topptekst sendt av klienten.

For eksempel hvis klienten ber om søknad / json, i tilfelle en feiltilstand, vil vi sørge for at vi returnerer et svarorgan kodet med søknad / json.

Den andre viktige implementeringsdetaljen er det vi returnerer a ModelAndView - dette er responsen, og det vil tillate oss å sette det som er nødvendig på det.

Denne tilnærmingen er en konsekvent og lett konfigurerbar mekanisme for feilhåndtering av en Spring REST-tjeneste.

Det har imidlertid begrensninger: Det samhandler med lavt nivå HtttpServletResponse og passer inn i den gamle MVC-modellen som bruker ModelAndView, så det er fortsatt rom for forbedring.

4. Løsning 3: @ControllerAdvice

Våren 3.2 gir støtte til en global @ExceptionHandler med @ControllerAdvice kommentar.

Dette muliggjør en mekanisme som bryter vekk fra den eldre MVC-modellen og bruker ResponseEntity sammen med typen sikkerhet og fleksibilitet @ExceptionHandler:

@ControllerAdvice offentlig klasse RestResponseEntityExceptionHandler utvider ResponseEntityExceptionHandler {@ExceptionHandler (verdi = {IllegalArgumentException.class, IllegalStateException.class}) beskyttet ResponseEntity handleConflict (RuntimeException-forespørsel = "WebSequest"), WebRequesp = body = "ApplicationResponse = body" = "". return handleExceptionInternal (ex, bodyOfResponse, nye HttpHeaders (), HttpStatus.CONFLICT, forespørsel); }}

De@ControllerAdvice kommentar tillater oss å konsolidere våre flere, spredt @ExceptionHandlers fra før til en enkelt, global feilhåndteringskomponent.

Selve mekanismen er ekstremt enkel, men også veldig fleksibel:

  • Det gir oss full kontroll over responsen, samt statuskoden.
  • Det gir kartlegging av flere unntak fra samme metode, som skal håndteres sammen.
  • Det gjør god bruk av den nyere RESTful ResposeEntity respons.

En ting å huske på her er å samsvarer med unntakene deklarert med @ExceptionHandler til unntaket brukt som argument for metoden.

Hvis disse ikke stemmer overens, vil ikke kompilatoren klage - ingen grunn til det - og Spring vil heller ikke klage.

Når unntaket faktisk kastes ved kjøretid, unntaksløsningsmekanismen vil mislykkes med:

java.lang.IllegalStateException: Ingen passende resolver for argument [0] [type = ...] HandlerMethod details: ...

5. Løsning 4: ResponseStatusException (Vår 5 og over)

Våren 5 introduserte ResponseStatusException klasse.

Vi kan opprette en forekomst av den som gir en HttpStatus og eventuelt a grunnen til og en årsaken:

@GetMapping (value = "/ {id}") offentlig Foo findById (@PathVariable ("id") Lang id, HttpServletResponse-svar) {prøv {Foo resourceById = RestPreconditions.checkFound (service.findOne (id)); eventPublisher.publishEvent (ny SingleResourceRetrievedEvent (dette, svar)); return resourceById; } catch (MyResourceNotFoundException exc) {throw new ResponseStatusException (HttpStatus.NOT_FOUND, "Foo Not Found", exc); }}

Hva er fordelene med å bruke ResponseStatusException?

  • Utmerket for prototyping: Vi kan implementere en grunnleggende løsning ganske raskt.
  • Én type, flere statuskoder: En unntakstype kan føre til flere forskjellige svar. Dette reduserer tett kobling sammenlignet med @ExceptionHandler.
  • Vi trenger ikke lage så mange tilpassede unntaksklasser.
  • Vi har mer kontroll over unntakshåndtering siden unntakene kan opprettes programmatisk.

Og hva med kompromissene?

  • Det er ingen enhetlig måte å håndtere unntak på: Det er vanskeligere å håndheve noen applikasjonsdekkende konvensjoner i motsetning til @ControllerAdvice, som gir en global tilnærming.
  • Kode duplisering: Vi kan finne oss i å replikere kode i flere kontrollere.

Vi bør også merke oss at det er mulig å kombinere forskjellige tilnærminger i en applikasjon.

For eksempel kan vi implementere en @ControllerAdvice globalt, men også ResponseStatusExceptions lokalt.

Vi må imidlertid være forsiktige: Hvis det samme unntaket kan håndteres på flere måter, kan vi merke noen overraskende oppførsel. En mulig konvensjon er å håndtere ett bestemt unntak alltid på en måte.

For mer informasjon og flere eksempler, se vår veiledning om ResponseStatusException.

6. Håndter tilgangen nektet i vårsikkerhet

Tilgang nektet oppstår når en autentisert bruker prøver å få tilgang til ressurser som han ikke har nok autoriteter til å få tilgang til.

6.1. MVC - Tilpasset feilside

La oss først se på MVC-stilen til løsningen og se hvordan du tilpasser en feilside for Access Denied.

XML-konfigurasjonen:

  ...  

Og Java-konfigurasjonen:

@ Override-beskyttet ugyldig konfigurasjon (HttpSecurity http) kaster unntak {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... .og () .exceptionHandling (). AccessDeniedPage ("/ min feil-side "); }

Når brukere prøver å få tilgang til en ressurs uten å ha nok autoriteter, blir de omdirigert til “/ Min-feil-side”.

6.2. Tilpasset AccessDeniedHandler

Deretter, la oss se hvordan vi kan skrive vår tilpassede AccessDeniedHandler:

@Komponent offentlig klasse CustomAccessDeniedHandler implementerer AccessDeniedHandler {@Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) kaster IOException, ServletException {respons.sendRedirect ("/ min-feil-side") }}

Og la oss nå konfigurere det ved hjelp av XML-konfigurasjon:

  ...  

0r ved hjelp av Java-konfigurasjon:

@Autowired privat CustomAccessDeniedHandler accessDeniedHandler; @ Override beskyttet ugyldig konfigurasjon (HttpSecurity http) kaster Unntak {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... og () .exceptionHandling (). AccessDeniedHandler (accessDeniedHandler) }

Legg merke til hvordan i vår CustomAccessDeniedHandler, kan vi tilpasse svaret slik vi ønsker ved å omdirigere eller vise en tilpasset feilmelding.

6.3. REST og sikkerhetsnivå på metodenivå

Til slutt, la oss se hvordan du håndterer sikkerhetsnivå på metodenivå @PreAuthorize, @PostAuthorize, og @Sikre Ingen tilgang.

Selvfølgelig vil vi bruke den globale mekanismen for unntakshåndtering som vi diskuterte tidligere for å håndtere AccessDeniedException også:

@ControllerAdvice offentlig klasse RestResponseEntityExceptionHandler utvider ResponseEntityExceptionHandler {@ExceptionHandler ({AccessDeniedException.class}) public ResponseEntity handleAccessDeniedException (Unntak ex, WebRequest-forespørsel) {return new ResponseEntity ("Tilgangsmelding)," Http. } ...}

7. Vårstøvelstøtte

Spring Boot gir en ErrorController implementering for å håndtere feil på en fornuftig måte.

I et nøtteskall serverer det en tilbakefallsside for nettlesere (også kjent som Whitelabel Error Page) og et JSON-svar for RESTful, ikke-HTML-forespørsler:

{"tidsstempel": "2019-01-17T16: 12: 45.977 + 0000", "status": 500, "error": "Intern serverfeil", "message": "Feil under behandling av forespørselen!", "path" : "/ mitt-endepunkt-med-unntak"}

Som vanlig tillater Spring Boot å konfigurere disse funksjonene med egenskaper:

  • server.error.whitelabel.enabled: kan brukes til å deaktivere Whitelabel Error Page og stole på servletbeholderen for å gi en HTML-feilmelding
  • server.error.include-stacktrace: med en alltid verdi; inkluderer stacktrace i både HTML og JSON standardrespons

Bortsett fra disse eiendommene, vi kan tilby vår egen visning-resolver kartlegging for /feil, overstyrer hvitelabelsiden.

Vi kan også tilpasse attributtene vi vil vise i svaret ved å inkludere et Feilattributter bønne i sammenheng. Vi kan utvide StandardErrorAttributes klasse levert av Spring Boot for å gjøre ting enklere:

@Komponent offentlig klasse MyCustomErrorAttributes utvider DefaultErrorAttributes {@Override public Map getErrorAttributes (WebRequest webRequest, boolean includeStackTrace) {Map errorAttributes = super.getErrorAttributes (webRequest, includeStackTrace); errorAttributes.put ("locale", webRequest.getLocale () .toString ()); errorAttributes.remove ("feil"); // ... return errorAttributter; }}

Hvis vi vil gå lenger og definere (eller overstyre) hvordan applikasjonen skal håndtere feil for en bestemt innholdstype, kan vi registrere en ErrorController bønne.

Igjen kan vi gjøre bruk av standard BasicErrorController levert av Spring Boot for å hjelpe oss.

Tenk deg for eksempel at vi vil tilpasse hvordan applikasjonen vår håndterer feil utløst i XML-sluttpunkter. Alt vi trenger å gjøre er å definere en offentlig metode ved hjelp av @RequestMapping, og sier at det produserer applikasjon / xml Media type:

@Komponent offentlig klasse MyErrorController utvider BasicErrorController {public MyErrorController (ErrorAttributes errorAttributes) {super (errorAttributes, new ErrorProperties ()); } @RequestMapping (produserer = MediaType.APPLICATION_XML_VALUE) offentlig ResponseEntity xmlError (HttpServletRequest-forespørsel) {// ...}}

8. Konklusjon

Denne artikkelen diskuterte flere måter å implementere en unntakshåndteringsmekanisme for en REST API på våren, startende med den eldre mekanismen og fortsatte med våren 3.2-støtte og inn i 4.x og 5.x.

Som alltid er koden presentert i denne artikkelen tilgjengelig på GitHub.

For vårsikkerhetsrelatert kode kan du sjekke vårsikkerhets-hvilemodulen.

HVILLE bunnen

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

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