SpringMVC校验机制参数顺序的坑

726 阅读2分钟
原文链接: github.com

问题

今天遇到一个小问题,在进行表单提交之后,直接进入了400错误页面,这个比较诡异,我所做的无非就是进行了简单的参数验证,提取BindingResult中的信息放到Model中方便前台显示,如下:

@RequestMapping(value = "/user/publish",method = RequestMethod.POST)
String publish(@Valid TopicForm topicForm,Model model,BindingResult result){
    if(result.hasErrors()){
        model.addAttribute("errors",result.allErrors)
        return "/user/publish"
    }
    Set<Tag> tagSet = tagService.constructeTags(topicForm.tags)
        topicService.publish(topicForm.build(tagSet))
    return "redirect:/"
}

解决过程

@Canonical
class TopicForm {


    @NotEmpty(message = "标题不能为空")
    @Length(min = 6, max = 125, message = "标题最少6个字符")
    String title

    @NotEmpty
    @Length(min = 15, max = 20, message = "内容必须在20-2W个字符哟")
    String content //故意改成20,产生验证错误

}

直接打断点,发现根本没进入这段代码,因此猜测是验证的时候报了异常,因此将@Lengthmax改成20,产生错误结果,然后进入org.hibernate.validator.internal.constraintvalidators.hv.LengthValidator,在isValid方法上右键Add to watches,打个断点(友情提示,本人用的IDEA-16),一步一步跟踪调试发现进入了下面这段关键的代码段:

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		String name = ModelFactory.getNameForParameter(parameter);
		Object attribute = (mavContainer.containsAttribute(name) ?
				mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

		WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
		if (binder.getTarget() != null) {
			bindRequestParameters(binder, webRequest);
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new BindException(binder.getBindingResult());
			}
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
	}
}

就是在这个地方抛出了一个BindException的异常,然后Spring进行了其他一些处理进去了400页面,不重要,我们看看这个判断条件,hasErrors()是用来判断是否有参数验证错误,这里很明显为true,下面还有个关键方法,我们进去一探究竟:

protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
  int i = methodParam.getParameterIndex();
  Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
  boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
  return !hasBindingResult;
}

这里一看就明晰了,getParameterIndex()获取的就是@Valid标注的方法参数索引,然会去判断紧跟其后的参数是否为Errors的子类,这时候我想到上面那个publish方法,我将Model作为其后续参数,而BindingResult为最后一个,因此肯定会返回true,导致抛出BindException异常。

总结

  1. 出了问题不要立马就去Google,先自己尝试去解决,打个断点进入源码调试下,进而分析问题可能产生的原因
  2. 平常注意看文档,对用到的东西要了若指掌