Penanganan pengecualian layanan Spring Boot REST

173

Saya mencoba menyiapkan server layanan REST skala besar. Kami menggunakan Spring Boot 1.2.1 Spring 4.1.5, dan Java 8. Pengontrol kami menerapkan @RestController dan anotasi @RequestMapping standar.

Masalah saya adalah bahwa Boot Musim Semi mengatur pengalihan default untuk pengecualian pengontrol ke /error. Dari dokumen:

Spring Boot menyediakan pemetaan / kesalahan secara default yang menangani semua kesalahan dengan cara yang masuk akal, dan terdaftar sebagai halaman kesalahan 'global' dalam wadah servlet.

Berasal dari bertahun-tahun menulis aplikasi REST dengan Node.js, bagi saya, ini tidak masuk akal. Pengecualian apa pun yang dihasilkan oleh titik akhir layanan harus kembali dalam respons. Saya tidak mengerti mengapa Anda akan mengirim arahan ulang ke apa yang kemungkinan besar konsumen Angular atau JQuery SPA yang hanya mencari jawaban dan tidak dapat atau tidak akan mengambil tindakan apa pun pada arahan ulang.

Apa yang ingin saya lakukan adalah menyiapkan penangan kesalahan global yang dapat mengambil pengecualian - baik sengaja dilemparkan dari metode pemetaan permintaan atau dihasilkan secara otomatis oleh Spring (404 jika tidak ada metode penangan ditemukan untuk tanda tangan jalur permintaan), dan mengembalikan respons kesalahan berformat standar (400, 500, 503, 404) ke klien tanpa pengalihan MVC. Secara khusus, kita akan mengambil kesalahan, log ke NoSQL dengan UUID, lalu kembali ke klien kode kesalahan HTTP yang tepat dengan UUID dari entri log di tubuh JSON.

Dokumen sudah tidak jelas tentang cara melakukan ini. Sepertinya saya bahwa Anda harus membuat implementasi ErrorController Anda sendiri atau menggunakan ControllerAdvice dengan cara tertentu, tetapi semua contoh yang saya lihat masih menyertakan penerusan respons ke semacam pemetaan kesalahan, yang tidak membantu. Contoh lain menunjukkan bahwa Anda harus membuat daftar setiap jenis Pengecualian yang ingin Anda tangani alih-alih hanya mencantumkan "Mudah Dibuang" dan mendapatkan semuanya.

Adakah yang bisa memberi tahu saya apa yang saya lewatkan, atau mengarahkan saya ke arah yang benar tentang cara melakukan ini tanpa menyarankan rantai yang Node.js akan lebih mudah untuk ditangani?

ogradyjd
sumber
6
Klien sebenarnya tidak pernah mengirim pengalihan. Redirect ditangani secara internal oleh wadah servlet (mis. Tomcat).
OrangeDog
1
Menghapus anotasi @ResponseStatus pada penangan pengecualian saya adalah yang saya butuhkan; lihat stackoverflow.com/questions/35563968/…
pmorken

Jawaban:

131

Jawaban baru (2016-04-20)

Menggunakan Spring Boot 1.3.1.RELEASE

Langkah Baru 1 - Mudah dan tidak terlalu mengganggu untuk menambahkan properti berikut ke aplikasi.properti:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

Jauh lebih mudah daripada memodifikasi instance DispatcherServlet yang ada (seperti di bawah ini)! - JO '

Jika bekerja dengan Aplikasi RESTful penuh, sangat penting untuk menonaktifkan pemetaan otomatis sumber daya statis karena jika Anda menggunakan konfigurasi default Spring Boot untuk menangani sumber daya statis maka penangan sumber daya akan menangani permintaan (itu dipesan terakhir dan dipetakan ke / ** yang artinya menerima permintaan yang belum ditangani oleh penangan lain dalam aplikasi) sehingga servlet pengirim tidak mendapat kesempatan untuk melemparkan pengecualian.


Jawaban Baru (2015-12-04)

Menggunakan Spring Boot 1.2.7.RELEASE

Langkah Baru 1 - Saya menemukan cara yang tidak terlalu mengganggu untuk mengatur flag "throExceptionIfNoHandlerFound". Ganti kode penggantian DispatcherServlet di bawah ini (Langkah 1) dengan ini di kelas inisialisasi aplikasi Anda:

@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
    private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }

Dalam hal ini, kami menetapkan bendera pada DispatcherServlet yang ada, yang mempertahankan setiap konfigurasi otomatis oleh kerangka Boot Spring.

Satu hal lagi yang saya temukan - penjelasan @EnableWebMvc mematikan Spring Boot. Ya, anotasi itu memungkinkan hal-hal seperti bisa menangkap semua pengecualian controller seperti dijelaskan di bawah, tetapi juga membunuh BANYAK konfigurasi otomatis yang bermanfaat yang biasanya diberikan oleh Spring Boot. Gunakan anotasi itu dengan sangat hati-hati saat Anda menggunakan Spring Boot.


Jawaban asli:

Setelah banyak penelitian dan menindaklanjuti solusi yang diposting di sini (terima kasih atas bantuannya!) Dan tidak ada jumlah kecil runtime menelusuri ke kode Spring, saya akhirnya menemukan konfigurasi yang akan menangani semua Pengecualian (bukan Kesalahan, tetapi baca terus) termasuk 404-an.

Langkah 1 - beri tahu SpringBoot untuk berhenti menggunakan MVC untuk situasi "handler not found". Kami ingin Spring melempar pengecualian alih-alih kembali ke klien, pandangan dialihkan ke "/ kesalahan". Untuk melakukan ini, Anda harus memiliki entri di salah satu kelas konfigurasi Anda:

// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
    @Bean  // Magic entry 
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet ds = new DispatcherServlet();
        ds.setThrowExceptionIfNoHandlerFound(true);
        return ds;
    }
}

Kelemahan dari ini adalah bahwa ia menggantikan servlet pengirim standar. Ini belum menjadi masalah bagi kami, tanpa ada efek samping atau masalah eksekusi yang muncul. Jika Anda akan melakukan hal lain dengan servlet operator karena alasan lain, ini adalah tempat untuk melakukannya.

Langkah 2 - Sekarang boot spring akan mengeluarkan eksepsi ketika tidak ada handler yang ditemukan, eksepsi itu dapat ditangani dengan yang lain dalam handler eksepsi yang disatukan:

@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        if(ex instanceof ServiceException) {
            errorResponse.setDetails(((ServiceException)ex).getDetails());
        }
        if(ex instanceof ServiceHttpException) {
            return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
        } else {
            return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Map<String,String> responseBody = new HashMap<>();
        responseBody.put("path",request.getContextPath());
        responseBody.put("message","The URL you have reached is not in service at this time (404).");
        return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
    }
    ...
}

Perlu diingat bahwa saya pikir anotasi "@EnableWebMvc" penting di sini. Tampaknya tidak ada yang berhasil tanpa itu. Dan itu saja - aplikasi boot Spring Anda sekarang akan menangkap semua pengecualian, termasuk 404, di kelas handler di atas dan Anda dapat melakukannya dengan sesuka Anda.

Satu poin terakhir - sepertinya tidak ada cara untuk mendapatkan ini untuk menangkap kesalahan yang dilemparkan. Saya memiliki ide aneh untuk menggunakan aspek-aspek untuk menangkap kesalahan dan mengubahnya menjadi Pengecualian yang dapat ditangani oleh kode di atas, tetapi saya belum punya waktu untuk benar-benar mencoba mengimplementasikannya. Semoga ini bisa membantu seseorang.

Setiap komentar / koreksi / peningkatan akan dihargai.

ogradyjd
sumber
alih-alih membuat bean servlet dispatcher baru, Anda dapat membalikkan flag dalam post processor: YourClass mengimplementasikan BeanPostProcessor {... `public Object postProcessBeforeInisialisasi (Object bean, String beanName) melempar BeansException {jika (contoh instance DispatcherServlet) {// jika tidak, kami dapatkan 404 sebelum handler pengecualian kami menghasilkan ((DispatcherServlet) bean) .setThrowExceptionIfNoHandlerFound (true); } kacang kembali; } Obyek publik postProcessAfterInisialisasi (Kacang objek, String beanName) melempar BeansException {return bean; }
wwadge
1
Saya memiliki masalah ini tetapi menyesuaikan DispatcherServlet tidak berhasil untuk saya. Apakah ada sihir tambahan yang diperlukan untuk Boot untuk menggunakan kacang dan konfigurasi tambahan ini?
IanGilham
3
@IanGilham Saya juga tidak bisa menggunakan Spring Boot 1.2.7. Saya bahkan tidak mendapatkan @ExceptionHandlermetode apa pun yang dipanggil saat menempatkannya ke dalam @ControllerAdvicekelas meskipun mereka berfungsi dengan baik jika ditempatkan di @RestControllerkelas. @EnableWebMvcada di kelas @ControllerAdvicedan @Configuration(saya menguji setiap kombinasi). Adakah ide atau contoh kerja? // @Andy Wilkinson
FrVaBe
1
Siapa pun yang membaca pertanyaan dan jawaban ini harus melihat pada Isu SpringBoot terkait di github .
FrVaBe
1
Tidak yakin @agpt. Saya memiliki proyek internal yang dapat saya naik ke 1.3.0 dan melihat apa efeknya pada pengaturan saya dan memberi tahu Anda apa yang saya temukan.
ogradyjd
41

Dengan Spring Boot 1.4+ ditambahkan kelas keren baru untuk penanganan pengecualian yang lebih mudah yang membantu menghilangkan kode boilerplate.

Yang baru @RestControllerAdvicedisediakan untuk penanganan pengecualian, ini adalah kombinasi dari @ControllerAdvicedan @ResponseBody. Anda dapat menghapus @ResponseBodypada @ExceptionHandlermetode saat menggunakan anotasi baru ini.

yaitu

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(value = { Exception.class })
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
        return new ApiErrorResponse(...);
    }
}

Untuk menangani 404 kesalahan menambahkan @EnableWebMvcanotasi dan yang berikut ke application.properties sudah cukup:
spring.mvc.throw-exception-if-no-handler-found=true

Anda dapat menemukan dan bermain dengan sumber-sumber di sini:
https://github.com/magiccrafter/spring-boot-exception-handling

pembuat sihir
sumber
7
Itu sangat membantu, terima kasih. Tapi saya tidak mengerti mengapa kita perlu `@EnableWebMvc` dengan `spring.mvc.throw-exception-if-no-handler-found = true`. Harapan saya adalah untuk menangani semua pengecualian melalui @RestControllerAdvicetanpa konfigurasi tambahan. Apa yang kulewatkan di sini?
fiskra
28

Saya pikir ResponseEntityExceptionHandlermemenuhi persyaratan Anda. Sepotong sampel kode untuk HTTP 400:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ResponseStatus(value = HttpStatus.BAD_REQUEST)
  @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
      HttpRequestMethodNotSupportedException.class})
  public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
    // ...
  }
}

Anda dapat memeriksa pos ini

Efe Kahraman
sumber
6
Saya telah melihat kode ini sebelumnya, dan setelah mengimplementasikannya, kelas menangkap pengecualian yang muncul dalam metode pemetaan permintaan pengontrol. Ini masih tidak menangkap 404 kesalahan, yang sedang ditangani dalam metode ResourceHttpRequestHandler.handleRequest, atau, jika penjelasan @EnableWebMvc digunakan, di DispatcherServlet.noHandlerFound. Kami ingin menangani kesalahan apa pun, termasuk 404, tetapi versi terbaru dari Boot Spring tampaknya sangat tumpul tentang cara melakukan itu.
ogradyjd
Saya menulis cara yang sama untuk menangani HttpRequestMethodNotSupportedExceptiondan plugin tabung yang sama di beberapa layanan mikro, untuk beberapa tujuan bisnis kita perlu menanggapi nama alias layanan mikro dalam respons. apakah ada cara kita bisa mendapatkan nama layanan mikro / nama pengontrol yang mendasarinya? Saya tahu HandlerMethodakan memberikan nama metode java dari mana pengecualian berasal. Tapi di sini, tidak ada metode yang menerima permintaan, maka HandlerMethodtidak akan diinisialisasi. Jadi, apakah ada solusi untuk menyelesaikan ini?
Paramesh Korrakuti
Saran pengontrol adalah pendekatan yang baik, tetapi selalu ingat bahwa pengecualian bukan bagian dari aliran yang harus terjadi dalam kasus luar biasa!
JorgeTovar
17

Meskipun ini adalah pertanyaan yang lebih tua, saya ingin membagikan pemikiran saya tentang ini. Saya harap, itu akan bermanfaat bagi sebagian dari Anda.

Saat ini saya sedang membangun REST API yang menggunakan Spring Boot 1.5.2.RELEASE dengan Spring Framework 4.3.7.RELEASE. Saya menggunakan pendekatan Java Config (sebagai lawan dari konfigurasi XML). Selain itu, proyek saya menggunakan mekanisme penanganan pengecualian global menggunakan @RestControllerAdviceanotasi (lihat nanti di bawah).

Proyek saya memiliki persyaratan yang sama dengan Anda: Saya ingin API REST saya mengembalikan sebuah HTTP 404 Not Founddengan muatan JSON yang menyertainya dalam respons HTTP ke klien API ketika mencoba mengirim permintaan ke URL yang tidak ada. Dalam kasus saya, muatan JSON terlihat seperti ini (yang jelas berbeda dari default Spring Boot, btw.):

{
    "code": 1000,
    "message": "No handler found for your request.",
    "timestamp": "2017-11-20T02:40:57.628Z"
}

Saya akhirnya membuatnya bekerja. Berikut adalah tugas utama yang perlu Anda lakukan secara singkat:

  • Pastikan bahwa NoHandlerFoundExceptiondilemparkan jika klien API memanggil URL yang tidak ada metode penangannya (lihat Langkah 1 di bawah).
  • Buat kelas kesalahan khusus (dalam kasus saya ApiError) yang berisi semua data yang harus dikembalikan ke klien API (lihat langkah 2).
  • Buat penangan pengecualian yang bereaksi pada NoHandlerFoundException dan mengembalikan pesan kesalahan yang tepat ke klien API (lihat langkah 3).
  • Tulis tes untuk itu dan pastikan, itu berhasil (lihat langkah 4).

Ok, sekarang ke detail:

Langkah 1: Konfigurasikan application.properties

Saya harus menambahkan dua pengaturan konfigurasi berikut ke file proyek application.properties:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

Ini memastikan, NoHandlerFoundException dilemparkan dalam kasus di mana klien mencoba mengakses URL yang tidak ada metode pengontrol yang dapat menangani permintaan.

Langkah 2: Buat Kelas untuk Kesalahan API

Saya membuat kelas yang mirip dengan yang disarankan dalam artikel ini di blog Eugen Paraschiv. Kelas ini merupakan kesalahan API. Informasi ini dikirim ke klien di badan respons HTTP jika terjadi kesalahan.

public class ApiError {

    private int code;
    private String message;
    private Instant timestamp;

    public ApiError(int code, String message) {
        this.code = code;
        this.message = message;
        this.timestamp = Instant.now();
    }

    public ApiError(int code, String message, Instant timestamp) {
        this.code = code;
        this.message = message;
        this.timestamp = timestamp;
    }

    // Getters and setters here...
}

Langkah 3: Membuat / Mengkonfigurasi Handler Pengecualian Global

Saya menggunakan kelas berikut untuk menangani pengecualian (untuk kesederhanaan, saya telah menghapus pernyataan impor, kode logging dan beberapa potongan kode lain yang tidak relevan):

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiError noHandlerFoundException(
            NoHandlerFoundException ex) {

        int code = 1000;
        String message = "No handler found for your request.";
        return new ApiError(code, message);
    }

    // More exception handlers here ...
}

Langkah 4: Tulis tes

Saya ingin memastikan, API selalu mengembalikan pesan kesalahan yang benar ke klien panggilan, bahkan dalam kasus kegagalan. Jadi, saya menulis tes seperti ini:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {

    public static final String ISO8601_DATE_REGEX =
        "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$";

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(roles = "DEVICE_SCAN_HOSTS")
    public void invalidUrl_returnsHttp404() throws Exception {
        RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
        mockMvc.perform(requestBuilder)
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.code", is(1000)))
            .andExpect(jsonPath("$.message", is("No handler found for your request.")))
            .andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
    }

    private RequestBuilder getGetRequestBuilder(String url) {
        return MockMvcRequestBuilders
            .get(url)
            .accept(MediaType.APPLICATION_JSON);
    }

The @ActiveProfiles("dev")penjelasan dapat dibiarkan pergi. Saya menggunakannya hanya karena saya bekerja dengan profil yang berbeda. Ini RegexMatcheradalah pencocokan Hamcrest khusus yang saya gunakan untuk lebih baik menangani bidang cap waktu. Ini kodenya (saya temukan di sini ):

public class RegexMatcher extends TypeSafeMatcher<String> {

    private final String regex;

    public RegexMatcher(final String regex) {
        this.regex = regex;
    }

    @Override
    public void describeTo(final Description description) {
        description.appendText("matches regular expression=`" + regex + "`");
    }

    @Override
    public boolean matchesSafely(final String string) {
        return string.matches(regex);
    }

    // Matcher method you can call on this matcher class
    public static RegexMatcher matchesRegex(final String string) {
        return new RegexMatcher(regex);
    }
}

Beberapa catatan selanjutnya dari pihak saya:

  • Di banyak posting lain di StackOverflow, orang menyarankan pengaturan @EnableWebMvcanotasi. Ini tidak perlu dalam kasus saya.
  • Pendekatan ini berfungsi baik dengan MockMvc (lihat tes di atas).
André Gasser
sumber
Ini memecahkan masalah bagi saya. Hanya untuk menambahkan, saya kehilangan anotasi @ RestControllerAdvice jadi saya menambahkannya bersama dengan anotasi @ ControllerAdvice sehingga akan menangani semua dan melakukan trik.
PGMacDesign
13

Bagaimana dengan kode ini? Saya menggunakan pemetaan permintaan fallback untuk menangkap 404 kesalahan.

@Controller
@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        //If exception has a ResponseStatus annotation then use its response code
        ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);

        return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @RequestMapping("*")
    public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
    }

    private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
        response.setStatus(httpStatus.value());

        ModelAndView mav = new ModelAndView("error.html");
        if (ex != null) {
            mav.addObject("title", ex);
        }
        mav.addObject("content", request.getRequestURL());
        return mav;
    }

}
Ludovic Martin
sumber
6

Secara default Spring Boot memberikan json dengan detail kesalahan.

curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
   "timestamp" : 1413313361387,
   "exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
   "status" : 400,
   "error" : "Bad Request",
   "path" : "/greet",
   "message" : "Required String parameter 'name' is not present"
}

Ini juga berfungsi untuk semua jenis kesalahan pemetaan permintaan. Lihat artikel ini http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Jika Anda ingin membuat log ke NoSQL. Anda dapat membuat @ControllerAdvice tempat Anda akan mencatatnya dan kemudian melemparkan kembali pengecualian. Ada contoh dalam dokumentasi https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

PaintedRed
sumber
DispatcherServlet default adalah hardcoded untuk melakukan redirect dengan MVC daripada melemparkan pengecualian ketika permintaan untuk pemetaan yang tidak ada diterima - kecuali Anda mengatur bendera seperti yang saya lakukan pada posting di atas.
ogradyjd
Selain itu, alasan kami menerapkan kelas ResponseEntityExceptionHandler adalah agar kami dapat mengontrol format jejak tumpukan log keluaran dan log masuk ke solusi NoSQL dan kemudian mengirim pesan kesalahan yang aman bagi klien.
ogradyjd
6

@RestControllerAdvice adalah fitur baru dari Spring Framework 4.3 untuk menangani Pengecualian dengan RestfulApi dengan solusi yang saling terkait:

 package com.khan.vaquar.exception;

import javax.servlet.http.HttpServletRequest;

import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;

/**
 * Handles exceptions raised through requests to spring controllers.
 **/
@RestControllerAdvice
public class RestExceptionHandler {

    private static final String TOKEN_ID = "tokenId";

    private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);

    /**
     * Handles InstructionExceptions from the rest controller.
     * 
     * @param e IntrusionException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IntrusionException.class)
    public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {       
        log.warn(e.getLogMessage(), e);
        return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
    }

    /**
     * Handles ValidationExceptions from the rest controller.
     * 
     * @param e ValidationException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ValidationException.class)
    public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);

        if (e.getUserMessage().contains("Token ID")) {
            tokenId = "<OMITTED>";
        }

        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getUserMessage());
    }

    /**
     * Handles JsonProcessingExceptions from the rest controller.
     * 
     * @param e JsonProcessingException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = JsonProcessingException.class)
    public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getOriginalMessage());
    }

    /**
     * Handles IllegalArgumentExceptions from the rest controller.
     * 
     * @param e IllegalArgumentException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = UnsupportedOperationException.class)
    public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles MissingServletRequestParameterExceptions from the rest controller.
     * 
     * @param e MissingServletRequestParameterException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request, 
                                                                        MissingServletRequestParameterException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles NoHandlerFoundExceptions from the rest controller.
     * 
     * @param e NoHandlerFoundException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.NOT_FOUND.value(), 
                                    e.getClass().getSimpleName(), 
                                    "The resource " + e.getRequestURL() + " is unavailable");
    }

    /**
     * Handles all remaining exceptions from the rest controller.
     * 
     * This acts as a catch-all for any exceptions not handled by previous exception handlers.
     * 
     * @param e Exception
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ErrorResponse handleException(HttpServletRequest request, Exception e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.error(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.INTERNAL_SERVER_ERROR.value(), 
                                    e.getClass().getSimpleName(), 
                                    "An internal error occurred");
    }   

}
vaquar khan
sumber
3

Untuk pengendali REST, saya akan merekomendasikan untuk menggunakan Zalando Problem Spring Web.

https://github.com/zalando/problem-spring-web

Jika Spring Boot bertujuan untuk menanamkan beberapa konfigurasi otomatis, pustaka ini melakukan lebih banyak untuk penanganan pengecualian. Anda hanya perlu menambahkan ketergantungan:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>LATEST</version>
</dependency>

Dan kemudian tentukan satu atau beberapa sifat saran untuk pengecualian Anda (atau gunakan yang disediakan secara default)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

    @ExceptionHandler
    default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
            final HttpMediaTypeNotAcceptableException exception,
            final NativeWebRequest request) {
        return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
    }

}

Kemudian Anda dapat menetapkan saran pengontrol untuk penanganan pengecualian sebagai:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}
JeanValjean
sumber
2

Untuk orang yang ingin merespons sesuai dengan kode status http, Anda dapat menggunakan ErrorControllercaranya:

@Controller
public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }

    @Override
    public ResponseEntity error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
            return ResponseEntity.status(status).body(ResponseBean.SERVER_ERROR);
        }else if (status.equals(HttpStatus.BAD_REQUEST)){
            return ResponseEntity.status(status).body(ResponseBean.BAD_REQUEST);
        }
        return super.error(request);
    }
}

Di ResponseBeansini adalah pojo kebiasaan saya untuk tanggapan.

Lym Zoy
sumber
0

Solusi dengan dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);dan @EnableWebMvc @ControllerAdvice berfungsi untuk saya dengan Spring Boot 1.3.1, sementara tidak berfungsi pada 1.2.7

Dennis R
sumber