我正在尝试对记录异常的代码进行分组,并在一些方法中呈现一个漂亮的视图。目前逻辑在@RequestHandler
本身的某个时刻(在 catch 块中),其他任务被委托给一个实用程序类(它可以工作,但会将逻辑从远离它的地方移开)抛出异常)。
Spring的@ExceptionHandler
似乎是将所有内容组合在一个地方(控制器本身或父级)的方法,并且摆脱了一些代码(不需要在 try-catch 中放置逻辑并且不需要实用程序类... ...直到我意识到@ExceptionHandler
方法不会自动装配ModelMap
或BindingResult
参数。目前,这些对象用于使用合理的错误消息呈现视图,我们也想记录这些对象中包含的一些信息。
为什么Spring不支持ModelMap
的{{1}}或BindingResult
等方法参数?它背后的理由是什么?
在Spring源代码(3.0.5)中,该方法的参数在@ExceptionHandler
中得到解决。请求处理程序抛出的异常会被捕获并重新抛出。 HandlerMethodInvoker.invokeHandlerMethod
及其参数在别处解决。作为一种解决方法,我想检查Exception是否实现了一个假设的“ModelAware”或“BindingResultAware”接口,在这种情况下,在重新抛出之前设置Model和BindingResult属性。
听起来怎么样?
答案 0 :(得分:4)
如前所述,您可以在控制器的某个方法中引发包装绑定结果对象的异常:
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 :(得分:1)
我刚才遇到了同样的问题。 ModelMap
或BindingResult
显式未在@ExceptionHandler
的JavaDocs中列为受支持的参数类型,因此这必须是故意的。
我认为抛出异常的原因可能会导致ModelMap
处于不一致状态。因此,根据您的情况,您可以考虑
BindingResult
移交给异常并稍后从异常中提取它以进行渲染BeanValidation
就好了)HTH
答案 2 :(得分:1)
改善第一个答案:
@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对象,如果你不需要为此目的创建一个特定的异常,你可以使用它。
答案 3 :(得分:0)
我有同样的问题来“添加”FunctinalException来ourthe BindingResult
要解决它,我们使用aop,如果控制器方法抛出运行时异常(或你想要的那个), aop捕获它并更新bindingresult或model(如果它们是方法的args)。
该方法必须使用包含错误路径的特定注释进行注释(如有必要,可针对特定异常进行配置)。
这不是最好的方法,因为开发人员不能忘记添加他不在其方法中使用的args但是Spring没有提供一个简单的系统来满足这种需求。
答案 4 :(得分:0)
我也想知道这一点。
为了以允许非全局错误视图显示可能抛出的任何ConstraintViolationException
的方式处理bean验证,我选择了@Stefan Haberl提出的解决方案:
明确捕获异常,告诉Spring MVC你知道你在做什么(你可以使用Template模式将异常处理逻辑重构到一个地方)
我创建了一个简单的Action
界面:
public interface Action {
String run();
}
并且可以很好地处理确保ActionRunner
工作的ConstraintViolationException
课程(基本上每个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";
});
}
这是为了包装那些我想要显式处理由于Bean验证而可能抛出的异常的动作方法。我仍然有一个全球@ExceptionHandler
,但这只涉及“哦垃圾”例外。
答案 5 :(得分:-1)
实际上确实如此,只需为@ExceptionHandler
创建一个MethodArgumentNotValidException
方法。
该类允许您访问BindingResult
对象。