SpringBoot 2.X整合JSR303服务端数据校验
一、JSR-303简介
-
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,注重于服务端数据校验,官方参考实现是Hibernate Validator。
-
Bean Validation 中内置的 constraint如下:
二、JSR-303基本校验
- 声明 constraint 的 JavaBean【User.java】
package com.hf.boot.jsr303.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: User
* @Author: hf
* @Date: 2020/7/2 13:40
* @Description: 用户实体信息
*/
@ApiModel(value = "User对象")
@Data
@Accessors(chain = true)
@NoArgsConstructor
public class User implements Serializable {
@ApiModelProperty(name = "userId", value = "用户id")
private Integer userId;
@NotEmpty(message = "用户名称必须填写")
@ApiModelProperty(name = "userName", value = "用户名称")
private String userName;
@NotEmpty(message = "登录名称必须填写")
@Length(min = 6,max = 12,message = "登录名称长度在6-12位")
@ApiModelProperty(name = "loginName", value = "登录名称")
private String loginName;
@NotEmpty(message = "登录密码不得为Null")
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$", message = "密码必须包含数字和字母、且在6-18位之间")
@ApiModelProperty(name = "password", value = "登录密码")
private String password;
@NotNull(message = "Icon头像字段必须填写")
@URL(message = "Icon链接符合规范")
@ApiModelProperty(name = "icon", value = "头像")
private String icon;
@NotNull(message = "用户邮箱必须填写")
@Email
@ApiModelProperty(name = "email", value = "邮箱")
private String email;
@ApiModelProperty(name = "phone", value = "手机号")
private String phone;
@ApiModelProperty(name = "state", value = "是否启用 0-启用 1-不启用", example = "0")
private int state;
@Min(0)
@Max(10000)
@NotEmpty
@ApiModelProperty(name = "sort", value = "排序字段", example = "0")
private int sort;
@ApiModelProperty(name = "createTime", value = "注册时间")
private String createTime;
}
- 使用@Valid开启校验功能、并获取校验结果。
/**
* 功能描述:
* 〈
* 用户保存,标注@Valid注解开启校验,给校验的Bean后面紧跟一个BindingResult就可以获取校验的结果。
* 〉
*
* @className: JSR303Controller
* @author: hf
* @version: 1.0.0
* @date: 2020/7/3 11:37
* @param: [user 用户实体, result 错误消息体]
* @resp: java.lang.Object
*/
@PostMapping("/saveUser")
public Object saveUser(@RequestBody @Valid User user, BindingResult result) {
HashMap<String, String> errorMap = new HashMap<>();
result.getFieldErrors().forEach(item -> {
//1.2 获取错误消息
String message = item.getDefaultMessage();
//1.3 获取错误的字段名
String field = item.getField();
errorMap.put(field, message);
});
if (errorMap.size() > 0) {
return Resp.fail(errorMap);
}
userService.saveUser(user);
return Resp.ok();
}
- 测试校验。
三、JSR-303自定义校验
- 创建自定义注解
package com.hf.boot.jsr303.annotation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: ListState
* @Author: hf
* @Date: 2020/7/3 13:39
* @Description: 自定义校验注解-校验用户状态信息
*/
@Documented
@Constraint(validatedBy = {ListStateConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListState {
/**
* 默认的错误消息【需要新建ValidationMessages.properties文件并配置】
*/
String message() default "{com.hf.boot.jsr303.annotation.ListState.message}";
/**
* 分组配置
*/
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 默认传递的值
*/
int[] values() default {};
}
- 自定义校验器
package com.hf.boot.jsr303.annotation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: ListStateConstraintValidator
* @Author: hf
* @Date: 2020/7/3 13:41
* @Description: 自定义校验校验器、定义规则
*/
public class ListStateConstraintValidator implements ConstraintValidator<ListState, Integer> {
private Set<Integer> set = new HashSet<>();
@Override
public void initialize(ListState constraintAnnotation) {
//获取注解上可以填的值
final int[] values = constraintAnnotation.values();
for (int val : values) {
set.add(val);
}
}
/**
* 功能描述:
* 〈
* 具体的校验规则定义
* 〉
*
* @className: ListStateConstraintValidator
* @author: hf
* @version: 1.0.0
* @date: 2020/7/3 13:50
* @param: [value 需要校验的值, context 上下文对象]
* @resp: boolean
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
- 使用注解
@ListState(values = {0, 1})
@ApiModelProperty(name = "state", value = "是否启用 0-启用 1-不启用", example = "0")
private int state;
- 测试注解。
四、JSR-303分组校验
- 创建分组【SaveGroup.java,UpdateGroup.java】
package com.hf.boot.jsr303.valid;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: SaveGroup
* @Author: hf
* @Date: 2020/7/3 14:14
* @Description: 用于数据保存-分组
*/
public interface SaveGroup {
}
package com.hf.boot.jsr303.valid;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: UpdateGroup
* @Author: hf
* @Date: 2020/7/3 14:15
* @Description: 用于数据修改-分组
*/
public interface UpdateGroup {
}
- 标注分组
/**
* 当用户新增时 userId必须为空、修改时 userId 不得为空
*
* 注: 当区分groups时、未标注groups的字段将不再校验
*/
@NotNull(message = "用户修改时【userId】不得为空", groups = {UpdateGroup.class})
@Null(message = "用户新增【userId】必须为空", groups = {SaveGroup.class})
@ApiModelProperty(name = "userId", value = "用户id")
private Integer userId;
- 标注@Validated注解开启分组校验功能
public Object saveUser(@RequestBody @Validated(SaveGroup.class) User user, BindingResult result) {
//......
return Resp.ok();
}
public Object updateUser(@RequestBody @Validated(UpdateGroup.class) User user, BindingResult result) {
//......
return Resp.ok();
}
- 测试校验
五、JSR-303 全局异常处理
- 新建异常处理类【JSR303ExceptionControllerAdvice.java】
package com.hf.boot.jsr303.exception;
import com.hf.boot.api.bean.Resp;
import com.hf.boot.api.constant.SysStatusCodeConstant;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
/**
* @Copyright (C), 2016-2020 HF
* @ClassName: JSR303ExceptionControllerAdvice
* @Author: hf
* @Date: 2020/7/2 11:16
* @Description: JSR303 系统异常 全局处理
*/
@RestControllerAdvice(basePackages = "com.hf.boot.jsr303.controller")
public class JSR303ExceptionControllerAdvice {
/**
* 功能描述:
* 〈
* 处理全局数据校验异常信息
* 〉
*
* @className: JSR303ExceptionControllerAdvice
* @author: hf
* @version: 1.0.0
* @date: 2020/7/3 15:17
* @param: [e 参数校验异常]
* @resp: java.lang.Object
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Object handleValidException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
HashMap<String, Object> errorMap = new HashMap<>();
//1.1 获取校验的错误结果
bindingResult.getFieldErrors().forEach(item -> {
//1.2 获取错误消息
String message = item.getDefaultMessage();
//1.3 获取错误的字段名
String field = item.getField();
errorMap.put(field, message);
});
return Resp.fail(SysStatusCodeConstant.CHECK_ERROR_CODE, "fail", errorMap);
}
/**
* 功能描述:
* 〈
* 处理其它的异常
* 〉
*
* @className: JSR303ExceptionControllerAdvice
* @author: hf
* @version: 1.0.0
* @date: 2020/7/3 15:21
* @param: [throwable]
* @resp: java.lang.Object
*/
@ExceptionHandler(value = Throwable.class)
public Object handleException(Throwable throwable) {
return Resp.fail(SysStatusCodeConstant.NO_KNOW_CODE, "未知异常、请联系管理员【" + throwable.getMessage() + "】");
}
}
- 控制层只需关注正常调用即可。
public Object updateUser(@RequestBody @Validated(UpdateGroup.class) User user) {
//不需要再指定BindingResult,关注正常逻辑接口调用即可......
return Resp.ok();
}
六、示例源码地址
- https://gitee.com/yx971072369/boot-s/tree/master/boot-jsr303