Spring Boot 1.4.3 и @ControllerAdvice

У меня есть приложение Spring 4 с ControllerAdvice, расширяющим ResponseEntityExceptionHandler, которое отлично работает при возникновении исключения.

@ControllerAdvice
@Slf4j
public class ExceptionManager extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatus status,
                                                                  WebRequest request) {
        final ValidationErrors errors = getErrors(ex.getBindingResult());
        headers.setContentType(MediaType.APPLICATION_JSON);
        return handleExceptionInternal(ex, errors, headers, HttpStatus.BAD_REQUEST, request);
    }


    @Override
    protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        final ValidationErrors errors = getErrors(ex.getBindingResult());
        headers.setContentType(MediaType.APPLICATION_JSON);
        return handleExceptionInternal(ex, errors, headers, HttpStatus.BAD_REQUEST, request);
    }

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity handleValidationException(ValidationException ex) {
        log.warn("Validation error", ex);
        return ex.toResponseEntity();
    }

    @ExceptionHandler(HttpClientErrorException.class)
    public ResponseEntity handleHttpClientErrorException(HttpClientErrorException ex) {
        log.error(ex.getMessage());
        return new ResponseEntity(ex.getResponseBodyAsString(), ex.getStatusCode());
    }

    @ExceptionHandler({Exception.class, RuntimeException.class})
    protected ResponseEntity<Object> handleRootException(Exception ex, WebRequest request) {
        return handleException(request, ex);
    }

...
}

Однако в случае исключения unmarshaller, например

Exception received: 
org.springframework.http.converter.HttpMessageNotReadableException: Could not unmarshal to [class com.company.etc.v1.PojoResponse]: null; nested exception is javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 28; Attribute name "entResponse" associated with an element type "assetDocum" must be followed by the ' = ' character.]
    at org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.readFromSource(Jaxb2RootElementHttpMessageConverter.java:149)
    at org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.readInternal(AbstractXmlHttpMessageConverter.java:61)
    at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:104)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
    at com.company.service.impl.SearchAsset.searchAssets(SearchAsset.java:42)
    ... 57 common frames omitted
Caused by: javax.xml.bind.UnmarshalException: null
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:563)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:249)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
    at org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.readFromSource(Jaxb2RootElementHttpMessageConverter.java:133)
    ... 127 common frames omitted
Caused by: org.xml.sax.SAXParseException: Attribute name "entResponse" associated with an element type "assetDocum" must be followed by the ' = ' character.
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
    at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177)
    ... 131 common frames omitted

исключение не поймано! Это происходит в конкретном случае, когда @RestController использует службу, которая вызывает внешнюю конечную точку. В моем случае внешняя служба возвращает XML, и если он поврежден (как в исключении выше), то SAXParseException не перехватывается и автоматически преобразуется весной как 400 Bad Request с пустым телом. Он также вообще не регистрируется.

Если в моем RestController я добавлю метод @ExceptionHandler(Throwable.class), он вызывается, и я могу контролировать отсылаемую ошибку.

Это нормально? Я думал, что ControllerAdvice может централизовать всю обработку исключений, избавляя от необходимости иметь ExceptionHandler в каждом контроллере.


person petronius    schedule 26.10.2017    source источник
comment
внутри метода handleRootExceptioninside передается Throwable, а не Exception ex.   -  person Vinay Prajapati    schedule 26.10.2017
comment
@VinayPrajapati Я обновил его до @ExceptionHandler(Throwable.class) @ResponseBody public ResponseEntity handleRootException(Throwable ex, WebRequest req) { log.error("Exception received: ", ex); return new ResponseEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR); }, но до сих пор не вызывается, поэтому этот точный код работает, если его поместить в контроллер.   -  person petronius    schedule 26.10.2017
comment
Вы не печатаете всю трассировку стека исключения, «вызвано» указывает, что это обернутое исключение, а не исключение верхнего уровня, которое фактически выдается.   -  person Gimby    schedule 26.10.2017
comment
@Gimby Я добавил полный стек при использовании @ExceptionHandler в контроллере. К вашему сведению, ответ XML прерывается возвратом строки в середине тега, чтобы спровоцировать исключение.   -  person petronius    schedule 26.10.2017
comment
Таким образом, фактическое перехватываемое исключение — это HttpMessageNotReadableException; если вы не обрабатываете это специально, следует вызвать общий метод handleRootException(). Этого не происходит?   -  person Gimby    schedule 26.10.2017
comment
Нет, он не вызывается, если помещен в ControllerAdvice. Стоит отметить, что я использую spring-boot-starter-actuator 1.4.3 в pom   -  person petronius    schedule 26.10.2017


Ответы (1)


Только что нашел решение: удалить файл extends ResponseEntityExceptionHandler. Похоже, это работает лучше, если вы @ControllerAdvice ничего не расширяете.

person petronius    schedule 26.10.2017
comment
На самом деле это не правильное решение. Он действительно поймает SAXParseException, но затем больше не поймает некоторые другие, например MethodArgumentNotValidException (я думаю, потому что ResponseEntityExceptionHandler.handleMethodArgumentNotValid больше не переопределяется - person petronius; 27.10.2017
comment
Да, действительно, событие при определении метода с аннотацией @ExceptionHandler(MethodArgumentNotValidException.class) не перехватывается. Ну, все же лучше, чем раньше... - person petronius; 22.11.2017