java注解校验参数

目录

一、参数校验的注解

1.validation-api中的注解

2. hibernate-validator中的注解

3.spring-context中的注解

二、注解的启用

三、使用

1.配置统一异常处理

2.配置统一响应类

3.配置状态码枚举类

4.实体类

5.校验分组处理器

6.controller类

7.使用场景

7.1指定校验场景

7.2嵌套校验

7.3集合校验

7.4自定义校验

编写约束校验器

快速失败(Fail Fast)


一、参数校验的注解

自带不需要导包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Java中参数校验的注解来自三方面,分别是

  1. javax.validation:validation-api,对应包javax.validation.constraints
  2. org.springframework:spring-context,对应包org.springframework.validation
  3. 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
  • Collection类型(List/Set)
  • String
@Pattern(regexp)限制必须符合regexp指定的正则表达式String
@Future限制必须是一个将来的日期

Date/Calendar

@Past限制必须是一个过去的日期

Date/Calendar

@Valid校验任何非原子类型,标记一个对象,表示校验对象中被注解标记的对象(不支持分组功能)需要校验成员变量的对象,比如@ModelAttribute标记的接口入参

2. hibernate-validator中的注解

注解说明适用类型
@Length(min,max)限制String类型长度必须在min和max之间,包含min和maxString, not null时才校验
@NotBlank验证注解的元素值不是空白(即不是null,且包含非空白字符)String
@NotEmpty验证注解的元素值不为null且不为空(即字符串非null且长度不为0、集合类型大小不为0)
  • Collection类型(List/Set)
  • String
@Range(min,max)验证注解的元素值在最小值和最大值之间
  • String(数字类型的字符串),非null时才校验
  • byte/short/int/long/float/double及其包装类,包装类非null时才校验
@Email(regexp)验证注解的字符串符合邮箱的正则表达式,可以使用regexp自定义正则表达式String
@CreditCardNumber验证银行借记卡、信用卡的卡号String(不能包含空格等特殊字符)

3.spring-context中的注解

注解说明适用类型
@Validated校验非原子类型对象,或启用类中原子类型参数的校验(支持分组校验;只校验包含指定分组的注解参数)
  • Controller类
  • @ModelAttribue标记的查询条件对象类
  • @RequestBody标记的请求体对象类

二、注解的启用

  1. 方法中对象参数中成员变量校验注解的生效条件
    1. @ModelAttribute标记的查询条件类参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
    2. @RequestBody标记的请求体对象参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
    3. @Valid或@Validated标记在方法或方法所属类上无效
  2. 方法中原子类型参数校验注解的生效条件
    1. @Validated标记在方法所属类上
  3. 按照分组启用
    1. 在注解中使用groups添加启用注解的分组
    2. 在@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();
    }
}

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值