Ниже приводится «ControllerAdvice», который действует как глобальный обработчик исключений для нескольких микросервисов .

// Custom Exception Handler
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ErrorResponse> customException(CustomException ex) {
    ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
    return new ResponseEntity<>(error, ex.getStatus());
}

@ExceptionHandler(value = Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse globalException(Exception ex) {
        
    return new ErrorMessage("INTERNAL_SERVER_ERROR", "500");    
}

Теперь для одного из моих MicroService мне нужно настроить ответ об ошибке, исходящий от вышеуказанного обработчика исключений, то есть мне нужно обернуть «ErrorResponse», исходящий из глобального обработчика исключений, в другой объект с именем «ErrorResponseWrapper»

class ErrorResponseWrapper extends ErrorResponse {

   String type;

   // getter & setters
}

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

@RestControllerAdvice
public class ControllerException extends GlobalExceptionHandler {

    // This Works FINE...
    @Override
    public ErrorResponseWrapper globalException(Exception ex) {
        // 
        ErrorResponse error = super.globalException(ex);

        // New Response Wrapper
        ErrorResponseWrapper wrapper = new ResponseWrapper();
        wrapper.setType("SOMETHING");
        
        return wrapper;
    }

//Issue is here:- 
//   I get Compilation Error: ReturnType is Not Compatible with the GlobalException return type.

    @Override
    public ResponseEntity<ErrorResponseWrapper> customException(CustomException ex) {
        
        ErrorResponse error = super.customException(ex).getBody();

        // New Response Wrapper
        ErrorResponseWrapper wrapper = new ResponseWrapper();
        wrapper.setType("SOMETHING");
    
        return new ResponseEntity<>(wrapper,ex.getStatus());
}

}

Он отлично работает с методом First Overridden, чем он отличается от ResponseEntity? Что я делаю неправильно, или как мне этого добиться?

0
ima.technophyle 23 Сен 2021 в 14:51

3 ответа

Лучший ответ

Ваша проблема в дженериках, и что они по умолчанию инвариантны, что сбивает с толку множество людей и может не сразу показаться логичным или правильным (но это так - продолжайте читать).

Есть эти 3 концепции: инвариант / ковариант / контравариант .

Ковариантный

Ковариантность означает, что подтип вещи так же хорош, как и сама вещь.

Базовая типизация Java ковариантна. Здесь:

Integer x = 5;
Number n = x; // This compiles and runs just great.

Обратите внимание, что типом x является целое число, которое является подтипом Number, и все же здесь это нормально. Это потому, что здесь применяется ковариация.

Контрвариация

Контравариантность - это обратное: супертип - это честная позиция. На самом деле это часть java: при переопределении методов вы можете использовать супертипы для параметров, и это нормально. Это компилирует:

public class Parent {
    public void foo(String x) {}
}
public class Child extends Parent {
    @Override public void foo(Object x) {}
}

Причина, по которой это нормально, должна быть очевидна: поскольку здравый смысл: все строки также являются объектами, поэтому метод foo, который определяет Child, может обрабатывать все возможные случаи вызова foo всякий раз, когда экземпляр Child рассматривается как Parent. Он может обрабатывать все это (это было бы, когда x является String) и многое другое для загрузки.

Обобщения и дисперсия

Дженерики по умолчанию инвариантны . Это головорез! Давайте проверим:

List<String> list1 = new ArrayList<String>();
List<Object> list2 = list1; // DOES NOT COMPILE!!

Это странно, правда? Но в этом есть смысл! В конце концов, если приведенный выше код работает, list1 и list2 оба указывают на один и тот же фактический список; поэтому, если вы внесете изменение в этот список с помощью ссылки list1, вы сможете увидеть это изменение при использовании ссылки list2. Но в этом и заключается проблема: я могу запустить list2.add(new Object()), но это будет означать, что list1.get() вернет Object, а не String.

Вот почему дженерики инвариантны: потому что так устроена вселенная.

Это касается везде. Таким образом, мы подошли к сути вашей проблемы:

ResponseEntity<ErrorResponseWrapper> не является подтипом ResponseEntity<ErrorResponse>!

К счастью, дженерики довольно гибкие. Вы можете иметь ковариацию и даже контравариантность, если хотите; просто попроси об этом. Вот теперь нормально работает:

public class Parent {
    public ResponseEntity<? extends ErrorResponse> foo() {}
}

public class Child extends Parent {
    @Override public ResponseEntity<? extends ErrorResponseWrapper> foo() {}
}

Это нормально работает. ? extends является java-ese для: я хочу обобщенные, но ковариантные. Тогда компилятор поможет вам и не даст вам разрушить мир. Давай попробуем:

List<String> list1 = new ArrayList<>();
List<? extends Object> list2 = list1; // now it compiles!
list2.add(new Object()); // but this does not!

Вызов .add() для любого List<? extends Y>, где Y может быть чем угодно, не компилируется , если вы не передаете литерал null и только потому, что null каждый тип. Это сделано специально - он позволяет вам назначить этот список строк для ссылки.

Исправление

Вернитесь к исходному типу и обновите возвращаемый тип. Если вы не можете этого сделать, вы ничего не можете здесь сделать.

2
rzwitserloot 23 Сен 2021 в 12:05

При определении CustomHandler вам необходимо использовать

@ExceptionHandler(value = CustomException.class)
public ResponseEntity<? extends ErrorResponse> customException(CustomException ex) {
    ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
    return new ResponseEntity<>(error, ex.getStatus());
} 
1
Supritam 23 Сен 2021 в 12:15

Попробуйте следующее в исходном методе customException:

@ExceptionHandler(value = CustomException.class)
public ResponseEntity<T extends ErrorResponse> customException(CustomException ex) {
    ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
    return new ResponseEntity<>(error, ex.getStatus());
}
0
João Dias 23 Сен 2021 в 12:25