Подтвердить что ты не робот

Как обрабатывать исключения в Spring MVC по-разному для запросов HTML и JSON

Я использую следующий обработчик исключений в Spring 4.0.3 для перехвата исключений и отображения пользовательской страницы ошибок пользователю:

@ControllerAdvice
public class ExceptionHandlerController
{
    @ExceptionHandler(value = Exception.class)
    public ModelAndView handleError(HttpServletRequest request, Exception e)
    {
        ModelAndView mav = new ModelAndView("/errors/500"));
        mav.addObject("exception", e);
        return mav;
    }
}

Но теперь мне нужна другая обработка запросов JSON, поэтому я получаю ответы об ошибках JSON для такого рода запросов при возникновении исключения. В настоящее время вышеуказанный код также запускается с помощью запросов JSON (с использованием заголовка Accept: application/json), а клиент JavaScript не нравится ответ HTML.

Как я могу обрабатывать исключения по-разному для запросов HTML и JSON?

4b9b3361

Ответ 1

Лучший способ сделать это (особенно в сервлете 3) - зарегистрировать страницу с контейнером и использовать ее для вызова Spring @Controller. Таким образом, вы можете обрабатывать разные типы ответов стандартным способом Spring MVC (например, с помощью @RequestMapping with производит =... для ваших клиентских компьютеров).

Я вижу из вашего другого вопроса, что вы используете Spring Boot. Если вы обновляетесь до моментального снимка (1.1 или лучше другими словами), вы получаете это поведение из коробки (см. BasicErrorController). Если вы хотите переопределить его, вам просто нужно сопоставить путь/ошибки к вашему собственному @Controller.

Ответ 2

В аннотации ControllerAdvice есть элемент/атрибут basePackage, который может быть установлен для определения, какие пакеты он должен сканировать для контроллеров и применять советы. Итак, что вы можете сделать, это отделить тех контроллеров, которые обрабатывают обычные запросы и тех, кто обрабатывает запросы AJAX, в разные пакеты, а затем пишут 2 контроллера обработки исключений с соответствующими аннотациями ControllerAdvice. Например:

@ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
@ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {

Ответ 3

Поскольку у вас есть HttpServletRequest, вы должны иметь возможность получить заголовок запроса "Принять". Затем вы можете обработать исключение на основе этого.

Что-то вроде:

String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
    // Process JSON exception
} else {
    ModelAndView mav = new ModelAndView("/errors/500"));
    mav.addObject("exception", e);
    return mav;
}

Ответ 4

В аннотации controlleradvice имеется несколько свойств, которые могут быть установлены, поскольку spring 4. Вы можете определить несколько советов экспертов, применяющих разные правила.

Одно свойство - это аннотации. Возможно, вы можете использовать конкретную аннотацию в сопоставлении запроса json или вы можете найти другое свойство более полезным?

Ответ 5

Использовать @ControllerAdvice Пусть обработчик исключений отправит DTO, содержащий ошибки поля.

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
    BindingResult result = ex.getBindingResult();
    List<FieldError> fieldErrors = result.getFieldErrors();

    return processFieldErrors(fieldErrors);
}

Этот код этого веб-сайта: http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/ Посмотрите там для получения дополнительной информации.

Ответ 6

Так как я не нашел для этого решения, я написал код, который вручную проверяет заголовок accept запроса для определения формата. Затем я проверяю, зарегистрирован ли пользователь и отправляет ли он полный стек, если он есть, или короткое сообщение об ошибке.

Я использую ResponseEntity, чтобы иметь возможность возвращать JSON или HTML, например здесь.
Код:

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {

    final HttpHeaders headers = new HttpHeaders();
    Object answer; // String if HTML, any object if JSON
    if(jsonHasPriority(request.getHeader("accept"))) {
        logger.info("Returning exception to client as json object");
        headers.setContentType(MediaType.APPLICATION_JSON);
        answer = errorJson(ex, isUserLoggedIn());
    } else {
        logger.info("Returning exception to client as html page");
        headers.setContentType(MediaType.TEXT_HTML);
        answer = errorHtml(ex, isUserLoggedIn());
    }
    final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
    return new ResponseEntity<>(answer, headers, status);
}

private String errorHtml(Exception e, boolean isUserLoggedIn) {
    String error = // html code with exception information here
    return error;
}

private Object errorJson(Exception e, boolean isUserLoggedIn) {
    // return error wrapper object which will be converted to json
    return null;
}

/**
 * @param acceptString - HTTP accept header field, format according to HTTP spec:
 *      "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
 * @return true only if json is the MIME type with highest quality of all specified MIME types.
 */
private boolean jsonHasPriority(String acceptString) {
    if (acceptString != null) {
        final String[] mimes = acceptString.split(",");
        Arrays.sort(mimes, new MimeQualityComparator());
        final String firstMime = mimes[0].split(";")[0];
        return firstMime.equals("application/json");
    }
    return false;
}

private static class MimeQualityComparator implements Comparator<String> {
    @Override
    public int compare(String mime1, String mime2) {
        final double m1Quality = getQualityofMime(mime1);
        final double m2Quality = getQualityofMime(mime2);
        return Double.compare(m1Quality, m2Quality) * -1;
    }
}

/**
 * @param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
 *      according to HTTP spec (missing mimeQuality means quality = 1).
 * @return quality of this pair according to HTTP spec.
 */
private static Double getQualityofMime(String mimeAndQuality) {
    //split off quality factor
    final String[] mime = mimeAndQuality.split(";");
    if (mime.length <= 1) {
        return 1.0;
    } else {
        final String quality = mime[1].split("=")[1];
        return Double.parseDouble(quality);
    }
}