Почему Spring MVC не позволяет выставлять Model или BindingResult @ExceptionHandler? - PullRequest
17 голосов
/ 23 июля 2011

Положение

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

Spring'ы @ExceptionHandler казались способом сгруппировать все в одном месте (сам контроллер или родительский элемент) и избавиться от некоторого кода (нет необходимости помещать логику в try-catch и не нужно для служебного класса) ... пока я не понял, что методы @ExceptionHandler не будут иметь параметры ModelMap или BindingResult с автопроводкой. В настоящее время эти объекты используются для визуализации представления с разумным сообщением об ошибке, и мы хотим зарегистрировать некоторую информацию, содержащуюся в этих объектах.

Вопрос

Почему Spring не поддерживает аргументы методов, такие как ModelMap или BindingResult для @ExceptionHandler? Что за этим стоит?

Возможное решение

В исходном коде Spring (3.0.5) аргументы для метода разрешены в HandlerMethodInvoker.invokeHandlerMethod. Исключение, сгенерированное обработчиком запроса, перехватывается и перебрасывается. @ExceptionHandler и его параметры разрешены в другом месте. В качестве обходного пути я подумал, чтобы проверить, реализует ли Exception гипотетический интерфейс «ModelAware» или «BindingResultAware», и в этом случае установить атрибуты Model и BindingResult, прежде чем перебрасывать его. Как это звучит?

Ответы [ 6 ]

4 голосов
/ 25 ноября 2014

Как указывалось ранее, вы можете вызвать исключение, заключающее объект результата привязки в некоторый метод вашего контроллера:

    if (bindingResult.hasErrors()) {
        logBindingErrors(bindingResult);
        //return "users/create";
        // Exception handling happens later in this controller
        throw new BindingErrorsException("MVC binding errors", userForm, bindingResult);
    }

С вашим исключением, определенным, как показано здесь:

public class BindingErrorsException extends RuntimeException {
    private static final Logger log = LoggerFactory.getLogger(BindingErrorsException.class); 
    private static final long serialVersionUID = -7882202987868263849L;

    private final UserForm userForm;
    private final BindingResult bindingResult;

    public BindingErrorsException(
        final String message, 
        final UserForm userForm, 
        final BindingResult bindingResult
    ) {
        super(message);
        this.userForm = userForm;
        this.bindingResult = bindingResult;

        log.error(getLocalizedMessage());
    }

    public UserForm getUserForm() {
        return userForm;
    }

    public BindingResult getBindingResult() {
        return bindingResult;
    }
}

ДалееВам просто нужно извлечь необходимую информацию из возникшего и пойманного исключения.Здесь при условии, что у вас есть подходящий обработчик исключений, определенный на вашем контроллере.Это может быть в совете диспетчера вместо этого или даже в другом месте.Для получения информации о подходящих и подходящих местах см. Документацию Spring.

@ExceptionHandler(BindingErrorsException.class)
public ModelAndView bindingErrors(
    final HttpServletResponse resp, 
    final Exception ex
) {
    if(ex instanceof BindingErrorsException) {
        final BindingErrorsException bex = (BindingErrorsException) ex;
        final ModelAndView mav = new ModelAndView("users/create", bex.getBindingResult().getModel());
        mav.addObject("user", bex.getUserForm());
        return mav;
    } else {
        final ModelAndView mav = new ModelAndView("users/create");
        return mav;            
    }
}
1 голос
/ 08 сентября 2015

Чтобы улучшить первый ответ:

    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public VndErrors methodArgumentNotValidException(MethodArgumentNotValidException ex, WebRequest request) {
    List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
    List<ObjectError> globalErrors = ex.getBindingResult().getGlobalErrors();
    List<VndError> errors = new ArrayList<>(fieldErrors.size() + globalErrors.size());
    VndError error;
    for (FieldError fieldError : fieldErrors) {
        error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(), fieldError.getField() + ", "
                + fieldError.getDefaultMessage());
        errors.add(error);
    }
    for (ObjectError objectError : globalErrors) {
        error = new VndError(ErrorType.FORM_VALIDATION_ERROR.toString(),  objectError.getDefaultMessage());
        errors.add(error);
    }
    return new VndErrors(errors);
}

Уже существует MethodArgumentNotValidException, в котором уже есть объект BindingResult, и вы можете использовать его, если вам не нужно создавать для этого конкретное исключение.

1 голос
/ 21 октября 2011

Я столкнулся с той же проблемой некоторое время назад. ModelMap или BindingResult явно не указаны в качестве поддерживаемых типов аргументов в JavaDocs @ExceptionHandler, поэтому это должно быть преднамеренно.

Я считаю, что причина в том, что генерация исключений в целом может привести к тому, что ваш ModelMap будет в несовместимом состоянии. Таким образом, в зависимости от вашей ситуации вы можете рассмотреть

  • Явно поймайте исключение, чтобы сообщить Spring MVC, что вы знаете, что делаете (вы можете использовать шаблон Template для рефакторинга логики обработки исключений в одном месте)
  • Если вы контролируете иерархию исключений, вы можете передать BindingResult исключению и извлечь его из исключения позже для целей визуализации
  • Во-первых, не выдавать исключение, а использовать некоторый код результата (как, например, BeanValidation)

НТН

0 голосов
/ 18 марта 2015

Мне тоже это интересно.

Для того, чтобы обрабатывать проверку bean-компонентов таким образом, чтобы представление неглобальной ошибки отображало любые ConstraintViolationException s, которые могут быть выброшены, я выбрал решение в соответствии с тем, что предложил @Stefan Haberl:

Явно поймайте исключение, чтобы сообщить Spring MVC, что вы знаете, что делаете (вы можете использовать шаблон Template для рефакторинга логики обработки исключений в одном месте)

Я создал простой Action интерфейс:

public interface Action {
  String run();
}

И класс ActionRunner, который обеспечивает правильную обработку ConstraintViolationException s (в основном сообщения от каждого ConstraintViolationException простодобавлено в Set и добавлено в модель):

public class ActionRunner {
  public String handleExceptions(Model model, String input, Action action) {
    try {
      return action.run();
    }
    catch (RuntimeException rEx) {
      Set<String> errors = BeanValidationUtils.getErrorMessagesIfPresent(rEx);
      if (!errors.isEmpty()) {
        model.addAttribute("errors", errors);
        return input;
      }
      throw rEx;
    }
  }
}

Java 8 делает это довольно приятным для запуска в методе действия контроллера:

@RequestMapping(value = "/event/save", method = RequestMethod.POST)
public String saveEvent(Event event, Model model, RedirectAttributes redirectAttributes) {
  return new ActionRunner().handleExceptions(model, "event/form", () -> {
    eventRepository.save(event);
    redirectAttributes.addFlashAttribute("messages", "Event saved.");
    return "redirect:/events";
  });
}

Это завершаетте методы действия, для которых я хотел бы явно обрабатывать исключения, которые могут быть выброшены из-за проверки бина.У меня все еще есть глобальный @ExceptionHandler, но это касается только исключений "о, дерьмо".

0 голосов
/ 19 сентября 2013

У меня была такая же проблема, чтобы "добавить" FunctinalException в наш результат привязки

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

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

Это не лучший способ, потому что разработчик не должен забывать добавлять аргументы, которые он не использует в своем методе, но Spring не предоставляет простой системы для этого.

0 голосов
/ 23 мая 2013

На самом деле это так, просто создайте метод @ExceptionHandler для MethodArgumentNotValidException.

Этот класс дает вам доступ к BindingResult объекту.

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