title: 数据校验之Spring和Hibernate validate tags:
- spring
- Hibernate Validator categories: spring date: 2017-06-25 18:18:56
目前系统中使用校验的地方比较多,前端校验&后端校验是绕不开的两个话题。
通常来说对于开发来说:后端校验是必备,前端校验是可选
1.前端验证可以没有,但后端验证必须要有。
原因:(1)可以通过某些工具绕过前端验证,后端验证是保证数据有效性的防线。
(2)前端验证有局限性。例如身份证号,需要调用api,需要后端进行验证。
2.前端验证也是必要的,可以优化用户的体验
前端验证不用提交数据,可以较快给出相应提示,用户体验比较好。
对于传统使用springmvc的项目通常可以使用spring validate来进行(实质还是使用hibernate validate)
那么具体分析一下代码。
正常使用springMVC项目通过注解Valid来实现校验
负责参数注入的实现类为
ErrorsMethodArgumentResolver 该参数解析器负责所有Errors接口的参数(一般使用BindResult)
public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
ModelMap model = mavContainer.getModel();
if (model.size() > 0) {
int lastIndex = model.size()-1;
String lastKey = new ArrayList(model.keySet()).get(lastIndex);
if (lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
}
}
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be declared immediately after the model attribute, " +
"the @RequestBody or the @RequestPart arguments to which they apply: " + parameter.getMethod());
}
复制代码
通常来说使用spring需要声明对应的validator
比如
<bean id="validateMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="classpath:validateMessages"/>
<property name="defaultEncoding" value="utf-8"/>
bean>
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="validateMessageSource"/>
bean>
复制代码
通常为了支持i18n还会声明对应的LocaleResolver
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="ln"/>
<property name="cookieMaxAge" value="#{3*60*60*1000}"/>
<property name="defaultLocale" value="zh"/>
bean>
复制代码
这样我们可以根据用户的cookie来定义用户的语言(虽然目前都是中文)
我们在校验对应的controler参数时在代码参数上增加Valid注解
else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
validate = true;
Object value = AnnotationUtils.getValue(paramAnn);
validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value});
}
复制代码
很明显代码中一旦方法注解包含Valid作为起始那么校验模块开启
WebDataBinder binder =
resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
}
args[i] = binder.getTarget();
if (assignBindingResult) {
args[i + 1] = binder.getBindingResult();
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
复制代码
判断下一个参数是否是Errors接口如果下一个参数是Errors接口就执行校验
private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate,
Object[] validationHints, boolean failOnErrors) throws Exception {
doBind(binder, webRequest);
if (validate) {
binder.validate(validationHints);
}
if (failOnErrors && binder.getBindingResult().hasErrors()) {
throw new BindException(binder.getBindingResult());
}
}
复制代码
校验对应的参数同时处理封装对应的bindResult
protected void processConstraintViolations(Set> violations, Errors errors) {
for (ConstraintViolation
可以看到 通过调用validateImpl(hibernate)获取对应的失败约束,同时将对应的值和message等等信息封装
if (!attrName.startsWith(BindingResult.MODEL_KEY_PREFIX) &&
(isSessionAttr || isBindingCandidate(attrValue))) {
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attrName;
if (mavModel != null && !model.containsKey(bindingResultKey)) {
WebDataBinder binder = createBinder(webRequest, attrValue, attrName);
initBinder(handler, attrName, binder, webRequest);
mavModel.put(bindingResultKey, binder.getBindingResult());
}
}
复制代码
当执行某些ArgumentResolver
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
if (argument != null) {
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return argument;
}
复制代码
明显modelAndView会将当前的bindingResult放入最后
而Errors获取参数
public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
ModelMap model = mavContainer.getModel();
if (model.size() > 0) {
int lastIndex = model.size()-1;
String lastKey = new ArrayList(model.keySet()).get(lastIndex);
if (lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
return model.get(lastKey);
}
}
throw new IllegalStateException(
"An Errors/BindingResult argument is expected to be declared immediately after the model attribute, " +
"the @RequestBody or the @RequestPart arguments to which they apply: " + parameter.getMethod());
}
复制代码
将末尾的参数获取,换言之 BindResults参数必须接在加了@Valid的相关某一个参数之后。
那么就可以成功的将对应的bindingResult赋值到指定的参数上。