很多时候,用户提交的表单数据需要验证其合法性。
比如不能为空,电话号码,邮箱格式等等,这些一般在js里面会有一次校验,选择表单input type也能帮助校验。
但是前端校验并不能保证到后台的数据就一定正确,在后台我们同样需要做校验。
这里我们来学习如何使用JSR303验证框架来校验表单/ajax提交的参数。
一,JSR 303是Java EE 6中的一项子规范,叫做Bean Validation,官方参考实现是hibernate Validator,此实现与Hibernate ORM没有任何关系。JSR 303用于对Java Bean中的字段的值进行验证。
这里我们引入依赖包:
org.hibernate
hibernate-validator
5.2.4.Final
二,构建映射实体类,UserInfoRequest
package com.maven.web.entity;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import com.maven.web.validator.NotRepeat;
/**
* 接收前端传递数据的映射类
* @author Administrator
*
*/
public class UserInfoRequest {
@NotNull
private String userName;
@Size(min=6, max=8)
private String password;
@Email
private String email;
private String phone;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "UserInfoRequest [userName=" + userName + ", password=" + password + ", email=" + email + ", phone="
+ phone + "]";
}
}
这里我们只对userName,password,email三个字段添加校验
三,构建测试类,ValidController
package com.maven.web.controller;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.maven.web.common.ErrorCode;
import com.maven.web.common.Result;
import com.maven.web.entity.UserInfoRequest;
import com.maven.web.validator.UserInfoValidator;
/**
* 对前端提交参数进行校验
* @author Administrator
*
*/
@RestController
@RequestMapping("/valid")
public class ValidController{
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 校验表单提交数据,或ajax模拟表单提交数据
* 没有添加参数BindingResult result,如果校验不通过则直接抛异常
* @param userInfo
* @return
*/
@RequestMapping(value="/form/post",method=RequestMethod.POST)
public Result validForm(@Valid UserInfoRequest userInfo){
logger.info("如果校验通过则打印参数:"+userInfo);
Result r = new Result(ErrorCode.SUCCESS);
return r;
}
/**
* 校验ajax传递的json字符串
* @param userInfo
* @return
*/
@RequestMapping(value="/ajax/post",method=RequestMethod.POST)
public Result validAjax(@RequestBody @Valid UserInfoRequest userInfo){
logger.info("如果校验通过则打印参数:"+userInfo);
Result r = new Result(ErrorCode.SUCCESS);
return r;
}
}
值得注意的是,注解@Valid要紧挨着需要校验的实体bean.
一般情况下,在需要校验的实体Bean后面还需要添加参数,BindingResult result,如下:
@RequestMapping(value="/form/post",method=RequestMethod.POST)
public Result validForm(@Valid UserInfoRequest userInfo,BindingResult result){
if (result.hasErrors()) {
return new Result(ErrorCode.PARAM_ERROR);
}
logger.info("如果校验通过则打印参数:"+userInfo);
Result r = new Result(ErrorCode.SUCCESS);
return r;
}
BindingResult 的作用是当参数不合法时能够捕捉到错误,不会直接抛异常。
鉴于我们之前有学习过如何处理公共异常,这里我们就让程序抛出异常,统一在公共异常中处理。
关于异常的处理,可以跳转这里查看:http://blog.csdn.net/mynoteblog/article/details/70046671
下面我直接贴出相关代码:
(1) Result
package com.maven.web.common;
import java.io.Serializable;
public class Result implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3998962529381113102L;
private int code;
private String msg;
private String detailMsg;
public Result() {
}
public Result(int code, String msg, String detailMsg) {
this.setCode(code);
this.setMsg(msg);
this.setDetailMsg(detailMsg);
}
public Result(ErrorCode errorCode) {
this(errorCode.getCode(), errorCode.getMessage(), "");
}
public Result(ErrorCode errorCode, String detailMsg) {
this(errorCode.getCode(), errorCode.getMessage(), detailMsg);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getDetailMsg() {
return detailMsg;
}
public void setDetailMsg(String detailMsg) {
this.detailMsg = detailMsg;
}
public void setCode(ErrorCode errorCode) {
this.setCode(errorCode.getCode());
this.setMsg(errorCode.getMessage());
}
}
(2) ErrorCode
package com.maven.web.common;
/**
* 错误码。
*
*/
public enum ErrorCode {
SUCCESS(0, "success"),
SYSTEM_ERROR(9999, "system error"),
UNKNOWN_ERROR(1000, "unknown error"),
PARAM_ERROR(1003, "parameter error"),
REQUEST_ERROR(1004, "request error");
private int code;
private String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
(3) DefaultExceptionHandler
package com.maven.web.exception;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.validation.UnexpectedTypeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.maven.web.common.ErrorCode;
import com.maven.web.common.Result;
import com.maven.web.entity.ValidFieldError;
/**
* 异常处理类。
*
*/
@ControllerAdvice
public class DefaultExceptionHandler{
private final static Logger logger = LoggerFactory.getLogger(DefaultExceptionHandler.class);
/**
* 参数不合法错误
*
* @param e
* @return
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public Result illegalArgumentException(IllegalArgumentException e) {
logger.error(e.getMessage());
return new Result(ErrorCode.PARAM_ERROR);
}
/**
* 缺少请求参数错误
*
* @param e
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
public Result MissingServletRequestParameterException(MissingServletRequestParameterException e) {
logger.error(e.getMessage());
return new Result(ErrorCode.PARAM_ERROR);
}
/**
* 请求类型错误
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseBody
public Result httpRequestMethodNotSupportedException(Exception e) {
logger.error(e.getMessage());
return new Result(ErrorCode.REQUEST_ERROR);
}
/**
* 参数校验错误
* form表单提交是BindException
* ajax json字符提交是MethodArgumentNotValidException
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Result bindException(Exception e) {
logger.error(e.getMessage());
BindingResult bindingResult = (e instanceof BindException) ? ((BindException)e).getBindingResult()
: ((MethodArgumentNotValidException)e).getBindingResult();
if (bindingResult != null && bindingResult.hasErrors()) {
List errors = bindingResult.getFieldErrors();
List validList = new ArrayList();
if (!(CollectionUtils.isEmpty(errors))) {
Iterator localIterator = errors.iterator();
while (localIterator.hasNext()) {
FieldError fe = (FieldError) localIterator.next();
validList.add(new ValidFieldError(fe));
}
}
logger.error("参数校验错误:"+validList.toString());
return new Result(ErrorCode.PARAM_ERROR, validList.toString());
}
return new Result(ErrorCode.SYSTEM_ERROR);
}
/**
* 其他异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result defaultException(Exception e) {
logger.error(e.getMessage());
if ((e instanceof BindException) || (e instanceof MethodArgumentNotValidException) || (e instanceof UnexpectedTypeException)) {
return bindException(e);
}
return new Result(ErrorCode.SYSTEM_ERROR);
}
}
(4) ValidFieldError
package com.maven.web.entity;
import java.io.Serializable;
import org.springframework.validation.FieldError;
/**
* 校验错误实体类
*
* @author Administrator
*
*/
public class ValidFieldError implements Serializable{
/**
*
*/
private static final long serialVersionUID = 5901446595499292806L;
private String field;
private String message;
private String code;
public ValidFieldError(FieldError fieldError){
setCode(fieldError.getDefaultMessage());
setField(fieldError.getField());
setMessage(fieldError.getDefaultMessage());
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "参数"+field+"错误:"+message;
}
}
准备工作做好了,下面我们来测试:
1) http://localhost:8088/com.maven.web/valid/form/post
2)http://localhost:8088/com.maven.web/valid/ajax/post
==================================================================================
上面我们使用的是默认的校验规则,很多时候,我们需要根据具体业务自定义校验规则,下面我们来看如何写自己的定义注解。
一,自定义注解,定义一个校验用户名不能重复的注解
package com.maven.web.validator;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UserNameValidator.class)
@Documented
public @interface NotRepeat {
String message() default "用户名已存在";
Class>[] groups() default {};
public abstract Class extends Payload>[] payload() default {};
}
二,在上面自定义的注解中,我们定义了一个UserNameValidator.class,用来处理校验逻辑
package com.maven.web.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 校验用户名
* @author Administrator
*
*/
public class UserNameValidator implements ConstraintValidator {
public void initialize(NotRepeat constraintAnnotation) {
// TODO Auto-generated method stub
}
/**
* 自定义用户名校验规则
* 假设数据库已有用户名"aa",如果用户再次提交相同姓名,则提示用户名重复
*/
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value.equals("aa")) {
return false;
}
return true;
}
}
三,将自定义的注解加到字段userName上面
@NotNull
@NotRepeat
private String userName;
再次测试:
http://localhost:8088/com.maven.web/valid/form/post
可以看到返回的提示消息,用户名未通过校验。
****************************************************************
除了JSR303验证方法,我们还可以使用spring validator,来自定义自己的校验规则。两者同时使用并不矛盾。
下面我们来看:
一,定义一个java类,实现Validator接口
package com.maven.web.validator;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.maven.web.entity.UserInfoRequest;
import com.maven.web.util.SpringUtils;
/**
* 使用spring validator校验数据
* @author Administrator
*
*/
@Component
public class UserInfoValidator implements Validator{
/**
* 这里绑定需要校验的java bean
*/
public boolean supports(Class> clazz) {
return UserInfoRequest.class.equals(clazz);
}
/**
* 校验逻辑
* 可以同时对多个属性进行校验
*/
public void validate(Object target, Errors errors) {
String msg = SpringUtils.getMessage("邮箱不能为空");
ValidationUtils.rejectIfEmpty(errors, "email",null,msg);
UserInfoRequest userInfo = (UserInfoRequest) target;
if (!userInfo.getPassword().equals("000000")) {
errors.rejectValue("password",null, SpringUtils.getMessage("用户名密码错误"));
}
}
}
这里我们定义两个规则,一是email不能为空,二是password不等于"000000"时表示未登录
规则定义了,下面是放到需要校验的请求里面使用。
在controller中定义initBinder方法,绑定上面我们自定义的校验规则。
package com.maven.web.controller;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.maven.web.common.ErrorCode;
import com.maven.web.common.Result;
import com.maven.web.entity.UserInfoRequest;
import com.maven.web.validator.UserInfoValidator;
/**
* 对前端提交参数进行校验
* @author Administrator
*
*/
@RestController
@RequestMapping("/valid")
public class ValidController{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private UserInfoValidator userInfoValidator;
/**
* 绑定UserInfoValidator
* spring validator 添加对UserInfo的校验
* @param webDataBinder
*/
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
webDataBinder.addValidators(userInfoValidator);
}
/**
* 校验表单提交数据,或ajax模拟表单提交数据
* 没有添加参数BindingResult result,如果校验不通过则直接抛异常
* @param userInfo
* @return
*/
@RequestMapping(value="/form/post",method=RequestMethod.POST)
public Result validForm(@Valid UserInfoRequest userInfo){
logger.info("如果校验通过则打印参数:"+userInfo);
Result r = new Result(ErrorCode.SUCCESS);
return r;
}
/**
* 校验ajax传递的json字符串
* @param userInfo
* @return
*/
@RequestMapping(value="/ajax/post",method=RequestMethod.POST)
public Result validAjax(@RequestBody @Valid UserInfoRequest userInfo){
logger.info("如果校验通过则打印参数:"+userInfo);
Result r = new Result(ErrorCode.SUCCESS);
return r;
}
}
下面我们再来测试:
http://localhost:8088/com.maven.web/valid/form/post
可以看到,校验不通过的提示又多了一条,密码不正确
大家可以自行测试,下篇将接着介绍spring 国际化消息处理,会在本篇博文上进行扩展。