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
;
@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开启校验功能、并获取校验结果。
@PostMapping("/saveUser")
public Object
saveUser(@RequestBody @Valid User user
, BindingResult result
) {
HashMap
<String, String> errorMap
= new HashMap<>();
result
.getFieldErrors().forEach(item
-> {
String message
= item
.getDefaultMessage();
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
;
@Documented
@Constraint(validatedBy
= {ListStateConstraintValidator
.class})
@Target({METHOD
, FIELD
, ANNOTATION_TYPE
, CONSTRUCTOR
, PARAMETER
, TYPE_USE
})
@Retention(RUNTIME
)
public @
interface ListState {
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
;
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
);
}
}
@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
;
public interface SaveGroup {
}
package com
.hf
.boot
.jsr303
.valid
;
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
;
@RestControllerAdvice(basePackages
= "com.hf.boot.jsr303.controller")
public class JSR303ExceptionControllerAdvice {
@ExceptionHandler(value
= MethodArgumentNotValidException
.class)
public Object
handleValidException(MethodArgumentNotValidException e
) {
BindingResult bindingResult
= e
.getBindingResult();
HashMap
<String, Object> errorMap
= new HashMap<>();
bindingResult
.getFieldErrors().forEach(item
-> {
String message
= item
.getDefaultMessage();
String field
= item
.getField();
errorMap
.put(field
, message
);
});
return Resp
.fail(SysStatusCodeConstant
.CHECK_ERROR_CODE
, "fail", errorMap
);
}
@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