目录
一、参数校验的注解
自带不需要导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Java中参数校验的注解来自三方面,分别是
- javax.validation:validation-api,对应包javax.validation.constraints
- org.springframework:spring-context,对应包org.springframework.validation
- org.hibernate:hibernate-validator,对应包org.hibernate.validator.constraints
1.validation-api中的注解
| 注解 | 说明 | 适用类型 |
|---|---|---|
| @AssertFalse | 限制必须是false | boolean Boolean:not null时才校验 |
| @AssertTrue | 限制必须是true | boolean Boolean:not null时才校验 |
| @Max(value) | 限制必须为一个小于等于value指定值的整数,value是long型 | byte/short/int/long/float/double及其对应的包装类;包装类对象not null时才校验 |
| @Min(value) | 限制必须为一个大于等于value指定值的整数,value是long型 | byte/short/int/long/float/double及其对应的包装类;包装类对象not null时才校验 |
| @DecimalMax(value) | 限制必须小于等于value指定的值,value是字符串类型 | byte/short/int/long/float/double及其对应的包装类;包装类对象not null时才校验 |
| @DecimalMin(value) | 限制必须大于等于value指定的值,value是字符串类型 | byte/short/int/long/float/double及其对应的包装类;包装类对象not null时才校验 |
| @Digits(integer, fraction) | 限制必须为一个小数(其实整数也可以),且整数部分的位数不能超过integer,小数部分的位数不能超过fraction。integer和fraction可以是0。 | byte/short/int/long/float/double及其对应的包装类;包装类对象必须not null时才校验 |
| @Null | 限制只能为null | 任意对象类型(比如基本数据类型对应的包装类、String、枚举类、自定义类等);不能是8种基本数据类型 |
| @NotNull | 限制必须不为null | 任意类型(包括8种基本数据类型及其包装类、String、枚举类、自定义类等);但是对于基本数据类型,没有意义 |
| @Size(min, max) | 限制Collection类型或String的长度必须在min到max之间,包含min和max |
|
| @Pattern(regexp) | 限制必须符合regexp指定的正则表达式 | String |
| @Future | 限制必须是一个将来的日期 | Date/Calendar |
| @Past | 限制必须是一个过去的日期 | Date/Calendar |
| @Valid | 校验任何非原子类型,标记一个对象,表示校验对象中被注解标记的对象(不支持分组功能) | 需要校验成员变量的对象,比如@ModelAttribute标记的接口入参 |
2. hibernate-validator中的注解
| 注解 | 说明 | 适用类型 |
|---|---|---|
| @Length(min,max) | 限制String类型长度必须在min和max之间,包含min和max | String, not null时才校验 |
| @NotBlank | 验证注解的元素值不是空白(即不是null,且包含非空白字符) | String |
| @NotEmpty | 验证注解的元素值不为null且不为空(即字符串非null且长度不为0、集合类型大小不为0) |
|
| @Range(min,max) | 验证注解的元素值在最小值和最大值之间 |
|
| @Email(regexp) | 验证注解的字符串符合邮箱的正则表达式,可以使用regexp自定义正则表达式 | String |
| @CreditCardNumber | 验证银行借记卡、信用卡的卡号 | String(不能包含空格等特殊字符) |
3.spring-context中的注解
| 注解 | 说明 | 适用类型 |
|---|---|---|
| @Validated | 校验非原子类型对象,或启用类中原子类型参数的校验(支持分组校验;只校验包含指定分组的注解参数) |
|
二、注解的启用
- 方法中对象参数中成员变量校验注解的生效条件
- @ModelAttribute标记的查询条件类参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
- @RequestBody标记的请求体对象参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
- @Valid或@Validated标记在方法或方法所属类上无效
- 方法中原子类型参数校验注解的生效条件
- @Validated标记在方法所属类上
- 按照分组启用
- 在注解中使用groups添加启用注解的分组
- 在@Validated中指定启用的分组
三、使用
1.配置统一异常处理
package com.shczjt.api.config;
import com.baomidou.mybatisplus.extension.api.R;
import com.shczjt.api.base.Result;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.stream.Collectors;
/**
* @Author: zhangqingyun
* @CreateTime: 2022-09-14 15:57
* @Description: 全局异常捕获处理类
*/
@RestControllerAdvice
@Log4j2
public class GlobalExceptionHandler {
@Autowired
private IUserLogService userLogService;
/**
* @description: 参数校验异常统一处理,拦截 MethodArgumentNotValidException 异常
* @author:
* @date: 2022/9/15 11:38
* @param: [e]
* @return: com.baomidou.mybatisplus.extension.api.R
**/
@ResponseBody
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public Result handleValidException(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException=======>{}",
e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")));
return Result.fail(400,e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")));
}
/**
* @description: 约束校验异常统一处理
* @author:
* @date: 2022/9/15 11:38
* @param: [e]
* @return: com.baomidou.mybatisplus.extension.api.R
**/
@ResponseBody
@ExceptionHandler(value = {ConstraintViolationException.class})
public Result handleValidException(ConstraintViolationException e) {
log.error("ConstraintViolationException=======>{}", e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));
return Result.fail(400,e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")));
}
/**
* @description: ValidationException
* @author:
* @date: 2022/9/15 11:38
* @param: [e]
* @return: com.baomidou.mybatisplus.extension.api.R
**/
@ResponseBody
@ExceptionHandler(ValidationException.class)
public Result handleValidationException(ValidationException e) {
log.error("ValidationException=======>{}", e);
return Result.fail(500,e.getMessage());
}
/**
* @description: 拦截运行时异常
* @author:
* @date: 2022/9/14 16:17
* @param: [e]
* @return: com.baomidou.mybatisplus.extension.api.R
**/
@ExceptionHandler(value = RuntimeException.class)
public Result runtimeExceptionHandle(RuntimeException e, HttpServletRequest request) {
log.error("捕捉到运行时异常,{},异常类型:{}", e.getMessage());
return Result.fail(500, "未知错误:" + e.getMessage());
}
/**
* @description: 捕获系统级异常
* @author:
* @date: 2022/9/14 16:17
* @param: [th]
* @return: com.baomidou.mybatisplus.extension.api.R
**/
@ExceptionHandler(value = Throwable.class)
public Result throwableHandle(Throwable t, HttpServletRequest request){
LocalDateTime visitDate = LocalDateTime.now();
log.error("捕捉到Throwable异常,{},异常类型:{}", t.getMessage());
return Result.fail(500,"系统异常:"+t.getMessage());
}
}
2.配置统一响应类
package com.shczjt.api.base;
import lombok.Data;
import java.io.Serializable;
/**
* <p>
* 统一响应类
* </p>
*/
@Data
public class Result<T> implements Serializable {
//响应码
int code;
//响应信息
String message;
//响应数据
T data;
public Result(int code, String message) {
this.code = code;
this.message = message;
}
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
//响应失败
public static Result fail(int code,String message){
return new Result(code,message);
}
//响应成功 无数据
public static Result ok(){
SystemCode ok=SystemCode.OK;
return new Result(ok.code,ok.message);
}
//响应成功 有数据
public static <F> Result ok(F data){
SystemCode ok=SystemCode.OK;
return new Result(ok.code,ok.message,data);
}
}
3.配置状态码枚举类
package com.shczjt.api.base;
import lombok.Getter;
@Getter
public enum SystemCode {
OK(200, "成功"),
NAME_EXISTED(103, "不允许重名,注册失败"),
WRONG_PASSWORD(104, "密码错误"),
NEED_LOGIN(105, "用户未登录"),
NEED_ADMIN(107, "无管理员权限"),
STRING_LENGTH(112, "字符串长度过长"),
EMAIL_ILLEGAL(116, "邮箱输入不合法"),
CANNOT_EXCEED_CURRENT_DATE(117, "生日不能超过当前日期"),
FAIL_UPLOAD(126,"上传失败"),
STRING_NOT_MEME(130,"不能包含表情符号"),
PAYMENT_FAILURE(132,"支付失败!请重新支付"),
NO_BLANK(399, "昵称中不能有空格"),
EMPTY_STRING(400, "参数错误,为空或为null,上传参数为:"),
UNAUTHORIZED(401, "用户未登录,请登录"),
NO_User_MESSAGE(402,"没有该用户信息"),
SYSTEM_ERROR(403, "系统异常,请从控制台或日志中查看具体错误信息"),
LOSE_TOKEN(420, "登录超时,需要重新登录"),
ParameterValidError(501, "参数验证错误,上传的参数为:"),
AccessDenied(502,"用户没有权限访问"),
ERR_NOTE(702,"验证码发送失败"),
ERR_NOTE2(703,"验证码已过期"),
ERR_NOTE3(704,"验证码输入错误");
//状态码
int code;
//响应信息
String message;
SystemCode(int code, String message) {
this.code = code;
this.message = message;
}
}
4.实体类
package com.shczjt.api.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* <p>
* 用户表
* </p>
* @since 2022-07-26
*/
@Setter
@Getter
@Accessors(chain = true)
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 账号
*/
@TableField("user_id")
private String userId;
/**
* 用户名
*/
@TableField("name")
@NotBlank(message = "用户名不能为空")
@Length(max = 20,message = "用户名不能超过20")
private String name;
/**
* 昵称
*/
@TableField("nick_name",groups = SecondLevel.class)
@Length(max = 20,message = "用户名不能超过20")
private String nickName;
/**
* 性别
*/
@TableField("gender",groups = Stair.class)
@NotNull(message="性别不能为空")
private Long gender;
/**
* 生日
*/
@TableField("birthday")
private LocalDate birthday;
/**
* 手机号
*/
@TableField("phone")
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
@NotBlank(message = "手机号码不能为空")
private String phone;
/**
* 邮箱
*/
@TableField("email")
@Email(message = "邮箱格式错误")
private String email;
/**
* 头像
*/
@TableField("avatar_url")
private String avatarUrl;
/**
* 常住城市
*/
@TableField("address")
private String address;
/**
* 城市编码
*/
@TableField("adcode")
private String adcode;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 逻辑删除(1未删除 0已删除)
*/
@TableLogic
private Integer deleted;
}
5.校验分组处理器
package com.shczjt.api.groups;
import javax.validation.groups.Default;
/**
* @Author:
* @CreateTime: 2022-09-15 11:44
* @Description: TODO
*/
public interface Stair extends Default {
}
package com.shczjt.api.groups;
import javax.validation.groups.Default;
/**
* @Author:
* @CreateTime: 2022-09-15 11:45
* @Description: TODO
*/
public interface SecondLevel extends Default {
}
6.controller类
@RestController
@RequestMapping("/user")
public class UserController{
@Autowired
private UserService userService;
@PostMapping("/createUser")
public Result createUser(@Validated(Stair.class) @RequestBody User user){
userService.createUser(user);
return Result.ok();
}
@PostMapping("/editUser")
public Result editUser(@Validated(SecondLevel.class) @RequestBody User user){
userService.editUser(person);
return Result.ok();
}
@RestController
@RequestMapping("/user")
@Validated
public class UserController{
@Autowired
private UserService userService;
@GetMapping("/info")
public Result getUserById(@Min(value = 1, message = "缺少id") @RequestParam("id) int id){
return Result.ok().setData(userService.getUserById(id));
}
@GetMapping("/info1")
public Result getUserByName(@NotBlank( message = "缺少name") @RequestParam("name") String name){
return Result.ok().setData(userService.getUserByName(name));
}
7.使用场景
7.1指定校验场景
@Slf4j
@RestController
public class GroupValidController {
/**
* 新增操作,通过 AddGroup 来指定分组场景
*/
@PostMapping("group/valid/student/save")
public R save(@Validated(value = AddGroup.class) @RequestBody StudentDTO stu) {
log.info("保存学员信息,入参:{}", JSON.toJSONString(stu));
// 业务逻辑
return R.ok();
}
/**
* 修改操作,通过 UpdateGroup 来指定分组场景
*/
@PostMapping("group/valid/student/update")
public R update(@Validated(value = UpdateGroup.class) @RequestBody StudentDTO stu) {
log.info("修改学员信息,入参:{}", JSON.toJSONString(stu));
// 业务逻辑
return R.ok();
}
}
7.2嵌套校验
@Data
public class StudentDTO implements Serializable {
// 姓名
@NotBlank(message = "姓名必须填写")
private String name;
// 课程
@Valid
private List<Course> course;
@Data
public static class Course {
@NotBlank(message = "课程编码不能为空")
private String code;
@NotBlank(message = "课程名称不能为空")
@Length(min = 2, max = 10)
private String name;
}
}
7.3集合校验
如果接口请求体直接传递 JSON 数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用 java.util.Collection 下的 List 或者 Set 来接收数据,参数校验并不会生效。在这种情况下,我们需要使用自定义的 List 集合来接收参数,即包装 List 类型,并声明 @Valid 注解。
public class StudentList<E> implements List<E> {
@Delegate // @Delegate 为标记属性生成委托方法(lombok 1.18.6 版本以上)
@Valid
public List<E> list = new ArrayList<>();
// 一定要记得重写toString方法
@Override
public String toString() {
return list.toString();
}
}
如果,我们需要在一次请求中保存多个 StudentDTO 对象,我们在 Controller 层可以这么写:
@Slf4j
@RestController
public class CollectionValidController {
/**
* 请求体中发送 JSON 数组
*/
@PostMapping("collection/valid/saveList")
public R saveList(@RequestBody @Validated StudentList<StudentDTO> list) {
log.info("保存学员信息,入参:{}", JSON.toJSONString(list));
// 业务逻辑处理
return R.ok();
}
}
7.4自定义校验
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class }) // 指定校验器,这里不指定时,就需要在初始化时指定
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 默认的提示内容
String message() default "必须提交指定的值哦";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] values() default { };
}
在约束注解中我们需要通过 @Constraint(validatedBy = {}) 来指定校验器。
编写约束校验器
约束校验器需要实现 ConstraintValidator 接口
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
/**
* 初始化方法
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int val : values) {
set.add(val);
}
}
/**
* 判断是否校验成功
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
这样我们就可以使用 @ListValue 进行参数校验了!
快速失败(Fail Fast)
Spring Validation 默认会校验完所有字段,然后才抛出异常。但通常情况下我们希望遇到校验异常就立即返回,此时可以通过一些简单的配置,开启 Fali Fast 模式,一旦校验失败就立即返回。
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
3028

被折叠的 条评论
为什么被折叠?



