参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。
那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的。
在使用之前我们可以在Spring Boot工程引入该jar包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
简单示例
同样,我先举一个简单的例子来说明如何使用JSR 303来进行参数校验。假设一个情景,我们需要使用手机号码和密码,来登陆一个网站。
之前我们在LoginController中,可能需要做很多繁琐的验证,是否为空之类的,现在我们使用JSR 303参数校验,就能够优雅的解决这个问题。
LoginController.java 登陆部分映射
@RequestMapping("/do_login")
@ResponseBody
//必须用@Valid修饰LoginVo
public Result<Boolean> doLogin(HttpServletResponse response, @Valid LoginVo loginVo) {
log.info(loginVo.toString());
//登录
userService.login(response, loginVo);
return Result.success(true);
}
LoginVo.java
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
public class LoginVo {
@NotNull //不能为空
@IsMobile //@IsMobile是自定义校验规则,必须符合电话号码规范。见IsMobile类和IsMobileValidator类
private String mobile;
@NotNull //密码不能为空
@Length(max=32) //长度不能超过32
private String password;
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "LoginVo [mobile=" + mobile + ", password=" + password + "]";
}
}
一个 constraint 通常由 annotation 和相应的 constraint validator 组成,它们是一对多的关系。
@IsMobile是一个定制化的 constraint,由两个内置的 constraint 组合而成。看我们自己编写的IsMobile类和IsMobileValidator类。我们可以自己定义参数校验的规则
IsMobile.java
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
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, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class }) //调用IsMobileValidator来进行校验
public @interface IsMobile {
boolean required() default true;
String message() default "手机号码格式错误"; //如果校验不通过提示的信息
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
IsMobileValidator.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import com.seckill.util.ValidatorUtil;
//实现ConstraintValidator的两个方法
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required = false;
//初始化方法
public void initialize(IsMobile constraintAnnotation) {
required = constraintAnnotation.required();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if(required) {
return ValidatorUtil.isMobile(value); //
}else {
if(StringUtils.isEmpty(value)) { //如果为空,参数校验通过
return true;
}else { //如果不为空,检验手机号格式是否通过
return ValidatorUtil.isMobile(value);
}
}
}
}
简单的通过正则表达式来判断电话号码开头是否为1,长度是否合格。
ValidatorUtil.java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
public class ValidatorUtil {
private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}");
public static boolean isMobile(String src) {
if(StringUtils.isEmpty(src)) {
return false;
}
Matcher m = mobile_pattern.matcher(src);
return m.matches();
}
}
JSR 303 - Bean Validation中有很多内置的constraint
使用案例
public class Order {
// 必须不为 null, 大小是 10
@NotNull
@Size(min = 10, max = 10)
private String orderId;
// 必须不为空
@NotEmpty
private String customer;
// 必须是一个电子信箱地址
@Email
private String email;
// 必须不为空
@NotEmpty
private String address;
// 必须不为 null, 必须是下面四个字符串'created', 'paid', 'shipped', 'closed'其中之一
@NotNull
private String status;
// 必须不为 null
@NotNull
private Date createDate;
// 嵌套验证
@Valid
private Product product;
…
getter 和 setter
}
这样看来,我们将参数校验整合处理,是否比每次使用时再一个个去判断参数是否符合条件所写的代码更加优雅呢?