Пользовательская обработка ошибок в объекте Spring REST в клиенте - PullRequest
0 голосов
/ 17 сентября 2018

Tl; р;

У меня есть контроллер REST в Spring-Boot, который либо возвращает MyPojo.class, либо RESTError.class.

Затем я вызываю этот контроллер из другой службы Spring с RestTemplate и хочу получить либо объект MyPojo, либо объект RESTError.

Есть ли способ сделать это, кроме проверки необработанной строки и последующего синтаксического анализа?

Подробное описание

У меня есть две Spring Services.

Давайте назовем их «сервер-сервис» и «клиент-сервис». «Клиент-сервис» должен извлечь POJO из «сервер-сервис». Если что-то пошло не так (неверный параметр, отсутствующий параметр и т. Д.), Возвращается пользовательский объект ошибки, который детализирует ошибку с помощью «сообщения разработчика» и более широкого «пользовательского сообщения».

Таким образом, если произошла ошибка, я смогу отобразить «пользовательское сообщение» в пользовательском интерфейсе.

Это только последнее средство. Конечно, я должен сначала выполнить проверку в моем «клиент-сервисе», прежде чем я вызову «сервер-сервис». Тем не менее, представьте, что кто-то меняет «сервер-сервис» без моего ведома ...

"сервер-сервис" - Код

MyPojo

@Getter @Setter @Accessors(chain = true)
public class MyPojo extends MyPojoSuper{
    private String someString;

    //hashCode & equals & toString
}

RESTError

Ниже приведен объект пользовательской ошибки. Он содержит сообщение об ошибке для обычного пользователя и сообщение для разработчика.

@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Setter @Accessors(chain = true)
public class RESTError extends MyPojoSuper{

    private String errorMessage, developerMessage;

    //hashCode & equals & toString
}

@ ControllerAdvice

Класс ExceptionHandlerAdvice будет обрабатывать все сгенерированные исключения и возвращать RESTError.

@ControllerAdvice
@RequestMapping("/error")
@Slf4j
public class ExceptionHandlerAdvice {

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public RESTError missingServletRequestParameterException(MissingServletRequestParameterException ex) {

    log.info("A parameter['" + ex.getParameterName() +"'] is missing.", ex);
    RESTError error = new RESTError();
    error.setDeveloperMessage(ex.getMessage());
    error.setErrorMessage("The parameter['" + ex.getParameterName() +"'] is missing.");

    return error;
    }
}

"клиент-сервис - код"

Метод клиента, вызывающий «сервер-сервис»

public MyPojo getMyPojo(){
    restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());
    return restTemplate.getForObject(getURLWithParams(), MyPojo.class);
}

Клиентская часть RESTError

@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Setter @Accessors(chain = true)
public class RESTError extends MyPojoSuper{

    private String errorMessage, developerMessage;

    //hashCode & equals & toString
}

MyPojoSuper

@Getter @Setter @Accessors(chain = true)
public class MyPojoSuper{
    public boolean error;

    //equals & hashCode & toString
}

MyCustomResponseErrorHandler

@Slf4j
public class MyCustomResponseErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        if (response.getStatusCode() != HttpStatus.OK) {
            log.info("There was an error.");
            return true;
        }
        return false;
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        if (response.getStatusCode() != HttpStatus.OK) {
            log.info("There was an error.");
        }
    }
}

Проблема

Как мне обработать мой пользовательский RESTError?

Используя RestTemplate, я могу анализировать мой JSON только один раз и только один класс (либо MyPojo.class, либо RESTError.class). Поэтому я мог бы проанализировать строку, проверить строку, а затем снова обработать JSON в желаемый формат:

public MyPojoSuper getMyPojo() {
    ObjectMapper mapper = new ObjectMapper();
    restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());
    String res = restTemplate.getForObject(getURL(), String.class);

    try {
        if (res.contains("\"error\":true")) {
            return mapper.readValue(res, RESTError.class);
        } else {
            return mapper.readValue(res, MyPojo.class);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return new RESTResponse().setError(true);
}

Мне не нравится это решение, и я буду признателен за любые другие предложения.

Ответы [ 3 ]

0 голосов
/ 17 сентября 2018

Основываясь на комментарии @Andrew S, вот решение:

Можно зарегистрировать ResponseErrorHandler, создав класс, реализующий ResponseErrorHandler в пользовательском классе и зарегистрировав его с помощью RestTemplate:

@Slf4j
public class MyCustomResponseErrorHandler implements ResponseErrorHandler {

    private ObjectMapper mapper = new ObjectMapper();

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        if (response.getStatusCode() != HttpStatus.OK) {
            log.info("loggin stuff");
            return true;
        }
    return false;
    }

    @Override
    public void handleError(ClientHttpResponse response) throws     JsonParseException, JsonMappingException, IOException{
        if (response.getStatusCode() != HttpStatus.OK) {
            log.info("logging stuff");
            throw new RestErrorExeption(mapper.readValue(response.getBody(),     RESTError.class));
        }
    }
}

Как показано, этот класс теперь создает пользовательское исключение RestErrorExeption. Он содержит объект RESTError. Затем можно проанализировать JSON в теле ответа, вызвав mapper.readValue(response.getBody(), RESTError.class). (Mapper является экземпляром com.fasterxml.jackson.databind.ObjectMapper)

Затем можно поймать исключение и получить проанализированный JSON в виде POJO из RestErrorExeption:

public MyPojoSuper getMyPojo() {
    restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());

    try {
        return restTemplate.getForObject(getURLWithParams(), MyPojo.class);
    } catch (RestErrorExeption e) {
        log.info(e.getMessage());
        return e.getRestError();
    }
}

Обратите внимание, что RESTError.class и MyPojo.class нуждаются в MyPojoSuper.class в качестве родителя для правильной обработки.

0 голосов
/ 17 сентября 2018

Насколько я знаю, когда вы используете restTemplate, он выдает HttpStatusCodeException, если код ответа не равен 2xx.Вот почему я думаю, что вы должны поймать исключение.Как

    try {
      restTemplate.getForObject(url, MyPojo.class);
     } catch (HttpStatusCodeException e) {
         if (e.getStatusCode() == 404) {

           String body = e.getResponseBodyAsString();

           // do smth with error json
        }
    }
0 голосов
/ 17 сентября 2018

Ваш класс RESTError является подклассом класса MyPojoSuper

RESTError extends MyPojoSuper

А в своем try-catch вы специально ловите IOException

try {
        if (res.contains("\"error\":true")) {
            return mapper.readValue(res, RESTError.class);
        } else {
            return mapper.readValue(res, MyPojo.class);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

Можете ли вы поделиться своим кодом класса для MyPojoSuper.java, пожалуйста? Должен ли он быть хотя бы подклассом IOException, чтобы его можно было перехватить в вашем try catch.

Если я правильно понял вашу проблему.

...