@ControllerAdvice и @ExceptionHandler не запускаются для моего RestController

Чтобы иметь унифицированную обработку исключений во всем приложении, я использую обработку ошибок для REST с Spring решением №3, в котором используется @ControllerAdvice вместе с @ExceptionHandler.

Весенняя версия: 4.3.22.РЕЛИЗ

Версия Spring Boot: 1.5.19.RELEASE

Это загрузочное приложение Spring, и ниже приведена структура моего пакета.

src/main/java
  com.test.app.controller
     MyRestController.java       -- This is my Rest controller
  com.test.app.handler
     RestExceptionHandler.java   -- This is my ControllerAdvice class

Ниже приведен мой код ControllerAdvice, и один из контроллеров выдает InvalidDataException, но соответствующий @ExceptionHandler не вызывается. Вместо этого я получаю Unexpected 'e' в качестве тела ответа с http 400.

@ControllerAdvice
public class RestExceptionHandler {

    @ExceptionHandler(InvalidDataException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public @ResponseBody ErrorResponse handleValidationError(final InvalidDataException ex,
                                                             final WebRequest request) {
        log.error("InvalidDataException message:{} ", ex.getMessage());
        return getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    }


    private ErrorResponse getExceptionResponse(final String message, final Integer errorCode) {
        final ErrorResponse exceptionResponse = new ErrorResponse();
        exceptionResponse.setErrorCode(errorCode.toString());
        exceptionResponse.setErrorDescription(message);
        log.error("message:{}", exceptionResponse);
        return exceptionResponse;
    }
}

Я просмотрел другой пост на SO, а также на других форумах, где упоминалось об использовании @EnableWebMvc и @ComponentScan и т. д., но ничего не помогло. Может кто-нибудь, пожалуйста, помогите мне понять, что мне не хватает?

Ниже приведен мой Controller и соответствующий интерфейс.

@RestController
public class MyRestController implements MyApi {

    @Override
    public ResponseEntity<List<MyResponse>> myGet(@RequestHeader(value = "a") String a,
                                                               @RequestHeader(value = "b") String b,
                                                               @RequestHeader(value = "c") String c,
                                                               @RequestHeader(value = "d") String d,
                                                               @RequestHeader(value = "e") String e,
                                                               @RequestHeader(value = "f") String f,
                                                               @RequestHeader(value = "g") String g) {

      List<MyResponse> responses = service.getData(c, d, e, f); // This throws exception
      return new ResponseEntity<>(responses, HttpStatus.OK);
    }
}


@Validated
@Api(value = "My", description = "the My API")
//This is generated interface through swagger codegen
public interface MyApi {

    @ApiOperation(value = "", nickname = "myGet", notes = "", response = MyResponse.class, responseContainer = "List")
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "normal response", response = MyResponse.class, responseContainer = "List"),
        @ApiResponse(code = 400, message = "Request is invalid", response = ErrorResponse.class),
        @ApiResponse(code = 401, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 404, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 405, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 409, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorResponse.class),
        @ApiResponse(code = 503, message = "Service Unavailable", response = ErrorResponse.class) })
    @RequestMapping(value = "/v1/test",
        produces = { "application/json" }, 
        method = RequestMethod.GET)
    default ResponseEntity<List<MyResponse>> myGet(@ApiParam(value = "a" ,required=true) @RequestHeader(value="a", required=true) String a,
                                                   @ApiParam(value = "b" ,required=true) @RequestHeader(value="b", required=true) String b,
                                                   @ApiParam(value = "c" ,required=true) @RequestHeader(value="c", required=true) String c,
                                                   @ApiParam(value = "d" ,required=true) @RequestHeader(value="d", required=true) String d,
                                                   @ApiParam(value = "e" ,required=true) @RequestHeader(value="e", required=true) String e,
                                                   @ApiParam(value = "f" ,required=true) @RequestHeader(value="f", required=true) String f,
                                                   @ApiParam(value = "g" ,required=true) @RequestHeader(value="g", required=true) String g) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    ApiUtil.setExampleResponse(request, "application/json", "{  \"aNum\" : 0,  \"cNum\" : \"cNum\"}");
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }
}

Ниже приведен фрагмент кода из моего GlobalExceptionHandler

class GlobalExceptionHandler extends ExceptionHandlerExceptionResolver implements HandlerExceptionResolver, Ordered, InitializingBean {
    ...
    @Override
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        if (exception instanceof com.myframework.SystemException) {
            return new ServletInvocableHandlerMethod(this, exceptionMethods.get(com.myframework.SystemException.class.getName()));
        } else if (exception instanceof GenericApplicationException) {
            return new ServletInvocableHandlerMethod(this, exceptionMethods.get(com.myframework.GenericApplicationException.class.getName()));
        } else {
            return null;
        }
    }
    ....
}

person Learner    schedule 03.09.2019    source источник
comment
Покажите нам код вашего MyRestController.   -  person mentallurg    schedule 04.09.2019
comment
@mentallurg Я добавил код контроллера в вопрос, пожалуйста, посмотрите.   -  person Learner    schedule 04.09.2019
comment
«Вместо этого я получаю Unexpected 'e' as the' — это отображается в консоли в трассировке стека? Если да, пожалуйста, вставьте эту трассировку стека в вопрос.   -  person Matheus Cirillo    schedule 05.09.2019
comment
Я получаю Unexpected 'e' в теле ответа с HTTP-кодом 400. В консоли нет трассировки исключений.   -  person Learner    schedule 05.09.2019
comment
Так чего же вы ожидаете вместо этого? Похоже, ваш обработчик ошибок работает нормально. Неожиданное «е» — это сообщение об исключении, поступающее от вашей службы, оно возвращается с http400 вместо http500 для необработанных исключений.   -  person tkruse    schedule 07.09.2019
comment
Если вы не видите журнал ошибок, возможно, ваша конфигурация ведения журнала неверна.   -  person tkruse    schedule 08.09.2019
comment
также вы должны показать URL-адрес, который вы используете, когда получаете сообщение об ошибке, и, возможно, части конфигурации вашего основного приложения, где вы включаете как контроллер, так и ControllerAdvice.   -  person tkruse    schedule 08.09.2019
comment
какое исключение является выбрасыванием кода и как выглядит трассировка стека?   -  person Kalpesh Soni    schedule 09.09.2019
comment
@KalpeshSoni Я получаю неожиданное «e» в теле ответа с HTTP-кодом 400. В консоли нет трассировки исключений.   -  person Learner    schedule 09.09.2019


Ответы (3)


Он должен работать. Некоторые возможные причины, которые приводят к сбою, могут быть:

  1. RestExceptionHandler еще не объявлен как spring bean? @SpringBootApplication по умолчанию будет сканировать только бины Spring для регистрации в своем пакете и во всех его подпакетах.

  2. На самом деле контроллер выдает не InvalidDataException, а другое исключение?

В любом случае, я предлагаю вам внести следующие изменения, чтобы проверить, вызывается ли RestExceptionHandler.

В вашем классе основного приложения Spring Boot явно зарегистрируйте RestExceptionHandler как Spring bean, используя @Import

@SpringBootApplication
@Import(RestExceptionHandler.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Также в RestExceptionHandler также включите метод для перехвата наиболее общего Exception :

@ControllerAdvice
public class RestExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public ErrorResponse handleGenericException(final Exception ex ,final WebRequest request) {
        System.out.println("handleGenericException ....");
        return getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    }
}

Пожалуйста, дайте мне знать, будет ли вызываться RestExceptionHandler после внесения этих изменений.

person Ken Chan    schedule 09.09.2019
comment
Обязательно постараюсь и сообщу. - person Learner; 10.09.2019
comment
Привет @Ken, спасибо за ваш ответ. Поскольку мы используем внутреннюю структуру, я считаю, что это мешает обработке ошибок, и я вижу, что следующий класс из среды вызывается class GlobalExceptionHandler extends ExceptionHandlerExceptionResolver implements HandlerExceptionResolver, Ordered, InitializingBean {} и вызывается следующий метод из этого класса: @Override protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {..} - person Learner; 13.09.2019
comment
Да, я думаю, скорее всего, это мешает, например, ваш индивидуальный GlobalExceptionHandler может быть настроен на более высокий приоритет для разрешения исключения, чем встроенный, и он почему-то не может обрабатывать @ControllerAdvice.... - person Ken Chan; 13.09.2019
comment
Я дал @Order(Ordered.HIGHEST_PRECEDENCE) своему RestExceptionHandler, но даже тогда он не вызывается. Как заставить его вызывать мой обработчик? - person Learner; 13.09.2019
comment
Ваш настроенный GlobalExceptionHandler переопределяет getExceptionHandlerMethod(), который содержит логику вызова @ControllerAdvice bean-компонента. Я предполагаю, что вы переопределяете логику получения @ControllerAdvice? Поскольку вы не показываете больше кодов о том, как настроить обработчики исключений и как выглядит GlobalExceptionHandler, я могу только догадываться... - person Ken Chan; 13.09.2019
comment
Я добавил фрагмент кода из моего GlobalExceptionHandler в вопросе выше, дайте мне знать, если потребуется дополнительная информация. - person Learner; 13.09.2019
comment
У вас была возможность посмотреть мой комментарий выше? - person Learner; 17.09.2019
comment
Не уверен, почему вы переопределяете getExceptionHandlerMethod() в GlobalExceptionHandler, поскольку вы переопределяете логику вызова компонента @ControllerAdvice. Можете ли вы удалить их, чтобы увидеть, вызывается ли ваш @ControllerAdvice? - person Ken Chan; 17.09.2019
comment
Или измените return null на return super.getExceptionHandlerMethod( handlerMethod, exception) - person Ken Chan; 17.09.2019

Убедитесь, что у вас нет других пружинных компонентов, которые расширяются.

AbstractErrorController
person Kalpesh Soni    schedule 09.09.2019
comment
У меня нигде такого нет. - person Learner; 09.09.2019

Приведенный ниже совет контроллера должен решить проблему.

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

 @ExceptionHandler(value = {InvalidDataException.class})
 protected ResponseEntity<Object> handleInvalidDataException(
  RuntimeException ex, WebRequest request) {
    return new ResponseEntity<>(getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
}
person Suraj    schedule 09.09.2019
comment
Вот демонстрационный проект глобального обработчика ошибок в Spring Boot github.com/s2agrahari/global -excpetion-handler-spring-boot - person Suraj; 09.09.2019
comment
Не думайте, что нужно расширять ResponseEntityExceptionHandler . Я использую много @ControllerAdvice без расширения ResponseEntityExceptionHandler во многих проектах и ​​работаю очень хорошо.. - person Ken Chan; 09.09.2019
comment
Спасибо @KenChan за репортаж. Обновлен ответ - person Suraj; 09.09.2019