Xử lý ngoại lệ dịch vụ Spring Boot REST


172

Tôi đang cố gắng thiết lập một máy chủ dịch vụ REST quy mô lớn. Chúng tôi đang sử dụng Spring Boot 1.2.1 Spring 4.1.5 và Java 8. Các bộ điều khiển của chúng tôi đang triển khai @RestControll và các chú thích @RequestMapping tiêu chuẩn.

Vấn đề của tôi là Spring Boot thiết lập một chuyển hướng mặc định cho các ngoại lệ của bộ điều khiển /error. Từ các tài liệu:

Spring Boot cung cấp ánh xạ / lỗi theo mặc định xử lý tất cả các lỗi theo cách hợp lý và được đăng ký dưới dạng trang lỗi 'toàn cầu' trong vùng chứa servlet.

Đến từ nhiều năm viết các ứng dụng REST với Node.js, với tôi, đây là bất cứ điều gì ngoại trừ hợp lý. Bất kỳ ngoại lệ nào điểm cuối dịch vụ tạo ra sẽ trả về trong phản hồi. Tôi không thể hiểu tại sao bạn gửi chuyển hướng đến những người rất có thể là người tiêu dùng Angular hoặc JQuery SPA, người chỉ tìm kiếm câu trả lời và không thể hoặc sẽ không thực hiện bất kỳ hành động nào đối với chuyển hướng.

Những gì tôi muốn làm là thiết lập một trình xử lý lỗi toàn cầu có thể có bất kỳ ngoại lệ nào - được ném một cách có chủ đích từ phương thức ánh xạ yêu cầu hoặc được tạo tự động bởi Spring (404 nếu không tìm thấy phương thức xử lý nào cho chữ ký đường dẫn yêu cầu) và trả về phản hồi lỗi được định dạng chuẩn (400, 500, 503, 404) cho máy khách mà không có bất kỳ chuyển hướng MVC nào. Cụ thể, chúng tôi sẽ nhận lỗi, đăng nhập nó vào NoQuery bằng UUID, sau đó trả lại cho khách hàng mã lỗi HTTP đúng với UUID của mục nhập nhật ký trong phần thân JSON.

Các tài liệu đã mơ hồ về cách làm điều này. Đối với tôi, có vẻ như bạn phải tạo triển khai ErrorContoder của riêng mình hoặc sử dụng Trình điều khiển theo cách nào đó, nhưng tất cả các ví dụ tôi thấy vẫn bao gồm chuyển tiếp phản hồi cho một loại ánh xạ lỗi, điều này không giúp ích. Các ví dụ khác cho thấy bạn phải liệt kê mọi loại Ngoại lệ bạn muốn xử lý thay vì chỉ liệt kê "Có thể ném" và nhận mọi thứ.

Bất cứ ai cũng có thể cho tôi biết những gì tôi đã bỏ lỡ, hoặc chỉ cho tôi đi đúng hướng về cách thực hiện việc này mà không đề xuất chuỗi mà Node.js sẽ dễ xử lý hơn?


6
Các khách hàng không bao giờ thực sự được gửi một chuyển hướng. Chuyển hướng được xử lý bên trong bởi thùng chứa servlet (ví dụ Tomcat).
OrangeDog

1
Xóa các chú thích @ResponseStatus trên các trình xử lý ngoại lệ của tôi là điều tôi cần; xem stackoverflow.com/questions/35563968/ từ
pmorken 30/03/2017

Câu trả lời:


131

Câu trả lời mới (2016-04-20)

Sử dụng Spring Boot 1.3.1.RELEASE

Bước 1 mới - Thật dễ dàng và ít xâm phạm để thêm các thuộc tính sau vào application.properations:

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

Dễ dàng hơn nhiều so với sửa đổi phiên bản DispatcherServlet hiện tại (như bên dưới)! - JO '

Nếu làm việc với Ứng dụng RESTful đầy đủ, điều rất quan trọng là vô hiệu hóa ánh xạ tự động của tài nguyên tĩnh vì nếu bạn đang sử dụng cấu hình mặc định của Spring Boot để xử lý tài nguyên tĩnh thì trình xử lý tài nguyên sẽ xử lý yêu cầu (được yêu cầu cuối cùng và được ánh xạ tới / ** có nghĩa là nó nhận bất kỳ yêu cầu nào chưa được xử lý bởi bất kỳ trình xử lý nào khác trong ứng dụng), do đó, bộ điều phối servlet không có cơ hội ném ngoại lệ.


Câu trả lời mới (2015-12-04)

Sử dụng Spring Boot 1.2.7.RELEASE

Bước 1 mới - Tôi đã tìm thấy một cách ít xâm phạm hơn để thiết lập cờ "throException IfNoHandlerFound". Thay thế mã thay thế của DispatcherServlet bên dưới (Bước 1) bằng mã này trong lớp khởi tạo ứng dụng của bạn:

@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);
    }

Trong trường hợp này, chúng tôi sẽ đặt cờ trên DispatcherServlet hiện có, bảo tồn mọi cấu hình tự động theo khung Spring Boot.

Một điều nữa tôi đã tìm thấy - chú thích @EnableWebMvc gây chết người cho Spring Boot. Đúng, chú thích đó cho phép những thứ như có thể bắt được tất cả các ngoại lệ của bộ điều khiển như được mô tả bên dưới, nhưng nó cũng giết rất nhiều cấu hình tự động hữu ích mà Spring Boot thường cung cấp. Sử dụng chú thích đó hết sức thận trọng khi bạn sử dụng Spring Boot.


Câu trả lời gốc:

Sau rất nhiều nghiên cứu và theo dõi các giải pháp được đăng ở đây (cảm ơn vì đã giúp đỡ!) Và một lượng nhỏ thời gian chạy theo mã Spring, cuối cùng tôi đã tìm thấy một cấu hình sẽ xử lý tất cả Ngoại lệ (không phải Lỗi, nhưng đọc tiếp) bao gồm 404s.

Bước 1 - yêu cầu SpringBoot ngừng sử dụng MVC cho các tình huống "không tìm thấy trình xử lý". Chúng tôi muốn Spring ném một ngoại lệ thay vì quay lại máy khách chuyển hướng xem sang "/ error". Để làm điều này, bạn cần có một mục trong một trong các lớp cấu hình của bạn:

// 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;
    }
}

Nhược điểm của việc này là nó thay thế cho servlet bộ điều phối mặc định. Đây chưa phải là vấn đề đối với chúng tôi, không có tác dụng phụ hoặc vấn đề thực thi nào xuất hiện. Nếu bạn định làm bất cứ điều gì khác với servlet điều phối vì những lý do khác, thì đây là nơi để thực hiện chúng.

Bước 2 - Bây giờ khởi động mùa xuân sẽ đưa ra một ngoại lệ khi không tìm thấy trình xử lý nào, ngoại lệ đó có thể được xử lý với bất kỳ trình xử lý ngoại lệ hợp nhất nào:

@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);
    }
    ...
}

Hãy nhớ rằng tôi nghĩ chú thích "@EnableWebMvc" có ý nghĩa ở đây. Có vẻ như không ai trong số này làm việc mà không có nó. Và đó là - ứng dụng khởi động Spring của bạn bây giờ sẽ bắt được tất cả các ngoại lệ, bao gồm 404, trong lớp xử lý ở trên và bạn có thể làm với chúng khi bạn muốn.

Một điểm cuối cùng - dường như không có cách nào để có được điều này để bắt lỗi ném. Tôi có một ý tưởng kỳ quặc là sử dụng các khía cạnh để bắt lỗi và biến chúng thành Ngoại lệ mà đoạn mã trên có thể xử lý, nhưng tôi chưa có thời gian để thực sự thử thực hiện điều đó. Hy vọng điều này sẽ giúp được ai đó.

Bất kỳ ý kiến ​​/ sửa chữa / cải tiến sẽ được đánh giá cao.


thay vì tạo một bean servlet bộ điều phối mới, bạn có thể lật cờ trong bộ xử lý bài: YourClass triển khai BeanPostProcessor {... `đối tượng công khai postProcessB BeforeInitialization (Object bean, String beanName) ném BeansException {if (bean instanceof DispatcherServlet) nhận được 404 trước khi trình xử lý ngoại lệ của chúng ta khởi động ((DispatcherServlet) bean) .setThrowException IfNoHandlerFound (true); } đậu trở lại; } đối tượng công khai postProcessAfterInitialization (Object bean, String beanName) ném BeansException {return bean; }
wwadge

1
Tôi có vấn đề này nhưng việc tùy chỉnh DispatcherServlet không phù hợp với tôi. Có phép thuật bổ sung nào cần thiết cho Boot để sử dụng thêm bean và config này không?
IanGilham

3
@IanGilham Tôi cũng không làm việc này với Spring Boot 1.2.7. Tôi thậm chí không nhận được bất kỳ @ExceptionHandlerphương thức nào được gọi khi đặt nó vào @ControllerAdvicelớp mặc dù chúng hoạt động đúng nếu được đặt trong @RestControllerlớp. @EnableWebMvclà trên @ControllerAdvicevà lớp @Configuration(tôi đã thử nghiệm mọi sự kết hợp). Bất kỳ ý tưởng hoặc ví dụ làm việc? //
@Andy

1
Bất cứ ai đọc câu hỏi và câu trả lời này nên xem qua Vấn đề SpringBoot tương ứng trên github .
FrVaBe

1
Không chắc chắn @agpt. Tôi có một dự án nội bộ mà tôi có thể chuyển lên 1.3.0 và xem hiệu ứng trên thiết lập của tôi là gì và cho bạn biết những gì tôi tìm thấy.
ogradyjd

41

Với Spring Boot 1.4+ các lớp tuyệt vời mới để xử lý ngoại lệ dễ dàng hơn đã được thêm vào giúp loại bỏ mã soạn sẵn.

Một cái mới @RestControllerAdviceđược cung cấp để xử lý ngoại lệ, nó là sự kết hợp của @ControllerAdvice@ResponseBody. Bạn có thể loại bỏ các @ResponseBodytrên @ExceptionHandlerphương pháp khi sử dụng chú thích mới này.

I E

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

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

Để xử lý lỗi 404, thêm @EnableWebMvcchú thích và các điều sau vào application.properations là đủ:
spring.mvc.throw-exception-if-no-handler-found=true

Bạn có thể tìm và chơi với các nguồn tại đây:
https://github.com/magiccrafter/spring-boot-exception-handling


7
Điều đó thực sự hữu ích, cảm ơn bạn. Nhưng tôi không hiểu tại sao chúng ta cần `@EnableWebMvc` với `spring.mvc.throw-ngoại lệ-nếu-không-xử lý-tìm thấy = true`. Kỳ vọng của tôi là xử lý tất cả các ngoại lệ thông qua @RestControllerAdvicemà không cần cấu hình bổ sung. Tôi đang thiếu gì ở đây?
fiskra

28

Tôi nghĩ ResponseEntityExceptionHandlerđáp ứng yêu cầu của bạn. Một đoạn mã mẫu cho 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) {
    // ...
  }
}

Bạn có thể kiểm tra bài này


6
Tôi đã thấy mã này trước đây và sau khi thực hiện nó, lớp đã bắt được các ngoại lệ được nêu ra trong các phương thức yêu cầu của bộ điều khiển. Điều này vẫn không bắt được các lỗi 404, đang được xử lý trong phương thức ResourceHttpRequestHandler.handleRequest hoặc, nếu chú thích @EnableWebMvc được sử dụng, trong DispatcherServlet.noHandlerFound. Chúng tôi muốn xử lý bất kỳ lỗi nào, kể cả 404, nhưng phiên bản mới nhất của Spring Boot dường như vô cùng khó hiểu về cách thực hiện điều đó.
giácyjd

Tôi đã viết cùng một cách để xử lý HttpRequestMethodNotSupportedExceptionvà bổ sung cùng một jar trong nhiều dịch vụ vi mô, vì một số mục đích kinh doanh, chúng tôi cần trả lời tên bí danh của dịch vụ vi mô trong phản hồi. Có cách nào chúng ta có thể nhận được tên bộ điều khiển / tên bộ điều khiển vi dịch cơ bản không? Tôi biết HandlerMethodsẽ cung cấp tên phương thức java từ nơi ngoại lệ được bắt nguồn. Nhưng ở đây, không có phương thức nào nhận được yêu cầu, do đó HandlerMethodsẽ không được khởi tạo. Vậy có giải pháp nào để giải quyết điều này?
Paramesh Korrakuti

Lời khuyên của kiểm soát viên là một cách tiếp cận tốt, nhưng luôn nhớ rằng các ngoại lệ không phải là một phần của dòng chảy mà chúng phải xảy ra trong các trường hợp đặc biệt!
JorgeTovar

17

Mặc dù đây là một câu hỏi cũ hơn, tôi muốn chia sẻ suy nghĩ của tôi về điều này. Tôi hy vọng rằng nó sẽ hữu ích cho một số bạn.

Tôi hiện đang xây dựng API REST sử dụng Spring Boot 1.5.2.RELEASE với Spring Framework 4.3.7.RELEASE. Tôi sử dụng cách tiếp cận Cấu hình Java (trái ngược với cấu hình XML). Ngoài ra, dự án của tôi sử dụng cơ chế xử lý ngoại lệ toàn cầu bằng cách sử dụng @RestControllerAdvicechú thích (xem phần bên dưới).

Dự án của tôi có các yêu cầu giống như của bạn: Tôi muốn API REST của tôi trả về một HTTP 404 Not Foundtải trọng JSON đi kèm trong phản hồi HTTP cho máy khách API khi nó cố gắng gửi yêu cầu đến một URL không tồn tại. Trong trường hợp của tôi, tải trọng JSON trông như thế này (rõ ràng khác với mặc định của Spring Boot, btw.):

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

Cuối cùng tôi đã làm cho nó hoạt động. Dưới đây là các nhiệm vụ chính bạn cần làm ngắn gọn:

  • Đảm bảo rằng nó NoHandlerFoundExceptionđược ném nếu các máy khách API gọi URLS mà không có phương thức xử lý nào tồn tại (xem Bước 1 bên dưới).
  • Tạo một lớp lỗi tùy chỉnh (trong trường hợp của tôi ApiError) chứa tất cả dữ liệu sẽ được trả về máy khách API (xem bước 2).
  • Tạo một trình xử lý ngoại lệ phản ứng trên NoHandlerFoundException và trả về một thông báo lỗi thích hợp cho máy khách API (xem bước 3).
  • Viết một bài kiểm tra cho nó và đảm bảo, nó hoạt động (xem bước 4).

Ok, bây giờ đến chi tiết:

Bước 1: Cấu hình application.properations

Tôi đã phải thêm hai cài đặt cấu hình sau vào application.propertiestệp của dự án :

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

Điều này đảm bảo, NoHandlerFoundExceptionđược ném trong trường hợp khách hàng cố truy cập URL mà không có phương thức điều khiển nào tồn tại để có thể xử lý yêu cầu.

Bước 2: Tạo một lớp cho lỗi API

Tôi đã tạo một lớp tương tự như một đề xuất trong bài viết này trên blog của Eugen Paraschiv. Lớp này đại diện cho một lỗi API. Thông tin này được gửi đến máy khách trong phần phản hồi HTTP trong trường hợp có lỗi.

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...
}

Bước 3: Tạo / Cấu hình Trình xử lý ngoại lệ toàn cầu

Tôi sử dụng lớp sau để xử lý các trường hợp ngoại lệ (để đơn giản, tôi đã xóa các câu lệnh nhập, mã đăng nhập và một số đoạn mã không liên quan khác):

@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 ...
}

Bước 4: Viết bài kiểm tra

Tôi muốn đảm bảo, API luôn trả về các thông báo lỗi chính xác cho máy khách đang gọi, ngay cả trong trường hợp không thành công. Vì vậy, tôi đã viết một bài kiểm tra như thế này:

@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);
    }

Các @ActiveProfiles("dev")chú thích có thể được bỏ đi. Tôi chỉ sử dụng nó khi tôi làm việc với các hồ sơ khác nhau. Đây RegexMatcherlà một công cụ đối sánh Hamcrest tùy chỉnh mà tôi sử dụng để xử lý tốt hơn các trường dấu thời gian. Đây là mã (tôi tìm thấy nó ở đây ):

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);
    }
}

Một số lưu ý thêm từ phía tôi:

  • Trong nhiều bài đăng khác trên StackOverflow, mọi người đề nghị đặt @EnableWebMvcchú thích. Điều này là không cần thiết trong trường hợp của tôi.
  • Cách tiếp cận này hoạt động tốt với MockMvc (xem thử nghiệm ở trên).

Điều này giải quyết các vấn đề đối với tôi. Chỉ cần thêm vào, tôi đã thiếu chú thích @ RestControllAdvice vì vậy tôi đã thêm nó cùng với chú thích @ ControllerAdvice để nó xử lý tất cả và điều đó đã tạo ra mánh khóe.
PGMacDesign

13

Mã này thì sao? Tôi sử dụng ánh xạ yêu cầu dự phòng để bắt lỗi 404.

@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;
    }

}

6

Theo mặc định, Spring Boot cung cấp cho json các chi tiết lỗi.

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"
}

Nó cũng hoạt động cho tất cả các loại lỗi ánh xạ yêu cầu. Kiểm tra bài viết này http://www.jayway.com/2014/10/19/spring-boot-error-responses/

Nếu bạn muốn tạo đăng nhập nó vào NoQuery. Bạn có thể tạo @ControllAdvice nơi bạn sẽ đăng nhập và sau đó ném lại ngoại lệ. Có ví dụ trong tài liệu https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc


DispatcherServlet mặc định được mã hóa cứng để thực hiện chuyển hướng với MVC thay vì ném ngoại lệ khi nhận được yêu cầu ánh xạ không tồn tại - trừ khi bạn đặt cờ như tôi đã làm trong bài viết ở trên.
ogradyjd

Ngoài ra, lý do chúng tôi triển khai lớp FeedbackEntityExceptionHandler là để chúng tôi có thể kiểm soát định dạng của dấu vết ngăn xếp lỗi đầu ra và nhật ký đến một giải pháp NoQuery và sau đó gửi thông báo lỗi an toàn cho khách hàng.
ogradyjd

6

@RestControllAdvice là một tính năng mới của Spring Framework 4.3 để xử lý Ngoại lệ với RestfulApi bằng một giải pháp quan tâm xuyên suốt:

 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");
    }   

}

3

Đối với bộ điều khiển REST, tôi khuyên bạn nên sử dụng Zalando Problem Spring Web.

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

Nếu Spring Boot nhằm mục đích nhúng một số cấu hình tự động, thư viện này sẽ xử lý ngoại lệ nhiều hơn. Bạn chỉ cần thêm phụ thuộc:

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

Và sau đó xác định một hoặc nhiều đặc điểm lời khuyên cho các ngoại lệ của bạn (hoặc sử dụng các đặc điểm được cung cấp theo mặc định)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

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

}

Sau đó, bạn có thể xác định lời khuyên của bộ điều khiển để xử lý ngoại lệ là:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}

2

Đối với những người muốn phản hồi theo mã trạng thái http, bạn có thể sử dụng ErrorControllercách:

@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);
    }
}

Đây ResponseBeanlà pojo tùy chỉnh của tôi để đáp ứng.


0

Giải pháp với dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);@EnableWebMvc @ControllerAdvice đã làm việc với tôi với Spring Boot 1.3.1, trong khi không hoạt động trên 1.2.7

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.