为什么Spring MVC不允许将Model或BindingResult暴露给@ExceptionHandler?

bas*_*ode 17 exception-handling spring-mvc

情况

我正在尝试对记录异常的代码进行分组,并在一些方法中呈现一个漂亮的视图.目前,逻辑在@RequestHandler自身的某个时刻(在一个catch块中),其他任务被委托给一个实用程序类(它可以工作,但会将逻辑从抛出异常的地方移开).

Spring @ExceptionHandler似乎是将所有内容组合在一个地方(控制器本身或父级)并摆脱一些代码(不需要在try-catch中放置逻辑而不需要实用程序类)的方法......直到我意识到一种@ExceptionHandler方法不会有ModelMapBindingResult自动装配参数.目前,这些对象用于使用合理的错误消息呈现视图,并且我们还想记录这些对象中包含的一些信息.

为什么Spring不支持这样的方法参数ModelMap或者BindingResult用于@ExceptionHandler?它背后的理由是什么?

可能解决方案

在Spring源代码(3.0.5)中,该方法的参数在HandlerMethodInvoker.invokeHandlerMethod.请求处理程序抛出的异常会被捕获并重新抛出.它@ExceptionHandler和它的参数在其他地方得到解决.作为一种解决方法,我想检查Exception是否实现了一个假设的"ModelAware"或"BindingResultAware"接口,在这种情况下,在重新抛出之前设置Model和BindingResult属性.听起来怎么样?

小智 5

如前所述,您可以在控制器的某些方法中引发包装绑定结果对象的异常:

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

您的异常定义如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

接下来,您只需从引发然后捕获的异常中提取所需的信息。这里假设您在控制器上定义了一个合适的异常处理程序。它可能在控制器建议中,甚至在其他地方。有关合适的位置,请参阅 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;            
    }
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*erl 2

我不久前遇到了同样的问题。的 JavaDocs 中明确未将ModelMap或列为受支持的参数类型,因此这肯定是有意为之。BindingResult@ExceptionHandler

我认为其背后的原因是,一般抛出异常可能会使您ModelMap处于不一致的状态。所以根据你的情况你可以考虑

  • 显式捕获异常以告诉 Spring MVC 您知道自己在做什么(您可以使用模板模式将异常处理逻辑重构到一个位置)
  • 如果您控制异常层次结构,则可以将其移交BindingResult给异常并稍后从异常中提取以用于渲染目的
  • 首先不抛出异常,而是使用一些结果代码(就像BeanValidation示例一样)

华泰