以往的校验方式,通过在代码里写各种非空的判断,各种长度的限制,
例: private void validate(SysUser user){
if (StringUtils.isEmpty(user.getUserName())) {
throw new BusinessException(UserServiceError.SERVICE_CHECK_NAME_NULL.value(), UserServiceError.SERVICE_CHECK_NAME_NULL.displayName());
}
if (user.getUserName().length() > Constant.SIXTY) {
throw new BusinessException(UserServiceError.SERVICE_CHECK_NAME_LENGTH.getCode(), UserServiceError.SERVICE_CHECK_NAME_LENGTH.getMessage());
}
if (user.getAge()==null ) {
throw new BusinessException(UserServiceError.SERVICE_CHECK_AGE_NOT_NULL.getCode(), UserServiceError.SERVICE_CHECK_AGE_NOT_NULL.getMessage());
}
}
现在我们将通用为空、长度限制的校验抽取出来,通过注解的形式独立出来,如:
@NotBlank(message = "名称不能为空")
@Size(min = 1, max = 30, message = "名称长度必须在1-30之间")
然后按规则对注解进行解析,这个过程JSR、Hibernate已经做了,引过来加以使用
org.springframework.boot
spring-boot-starter-validation
定义实体类,在字段上用hibernate定义的注解标识非空及长度限制,如:
@Data
public class SysUser implements Serializable {
/**
*用户ID
*/
private Long userId;
/**
*名称
*/
@NotBlank(message = "名称不能为空")
@Size(min = 1, max = 30, message = "名称长度必须在1-30之间")
private String username;
/**
*密码
*/
private String password;
}
在接口中使用校验
public Result save(SysUser dto){
//效验数据
ValidatorUtils.validate(dto, Default.class);
sysUserService.save(dto);
return new Result();
}
Validator工具类封装
@Component
public class ValidatorUtils {
@Autowired
private Validator validator;
/**
* 校验对象
*/
public void validate(Object object, Class<?>... groups)
throws ValidatorException {
Set<ConstraintViolation> constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
ConstraintViolation constraint = constraintViolations.iterator().next();
throw new ValidatorException(constraint.getMessage());
}
}
}
以上这种方式,对业务的侵入较大,可以进行优化
实体类定义保持不变
@Data
public class SysUser implements Serializable {
/**
*用户ID
*/
private Long userId;
/**
*名称
*/
@NotBlank(message = "名称不能为空")
@Size(min = 1, max = 30, message = "名称长度必须在1-30之间")
private String username;
/**
*密码
*/
private String password;
}
定义注解,将需要进行校验的模块用该注解标识,去除了手动调用ValidatorUtils的烦恼,
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Validate {
public Class[] value() default Default.class;
}
定义切面,在定义注解Validate的地方统一进行校验处理,
@Component
@Aspect
public class ValidateAspect {
@Autowired
private Validator validator;
/**
* 配置切入点
*/
@Pointcut("@annotation(com.example.validate.annotation.Validate)")
public void logPointcut() {
}
@Around("logPointcut()")
public Object paramsAdvice(ProceedingJoinPoint joinPoint) {
try {
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return joinPoint.proceed();
} else {
for (Object arg : args) {
try {
Set<ConstraintViolation> validate = validator.validate(arg, Default.class);
if (!validate.isEmpty()) {
StringBuilder stringBuilder = new StringBuilder();
validate.iterator().forEachRemaining((value) -> {
stringBuilder.append(value.getMessage());
});
if(!StringUtils.isEmpty(stringBuilder.toString())){
throw new IllegalArgumentException(stringBuilder.toString());
}
}
} catch (Exception e) {
throw e;
}
}
}
return joinPoint.proceed();
} catch (Throwable e) {
throw new IllegalArgumentException(e.getMessage());
}
}
}
接口中仅需使用@Validate,即可实现自动校验
@PostMapping(value = "/user")
@Validate
public String addUser(@RequestBody SysUser sysUser){
return "ok";
}