Java中使用注解校验参数

一、参数校验的注解

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中的注解

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. hibernat

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中的注解

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

二、注解的启用

  1. 方法中对象参数中成员变量校验注解的生效条件
    1. @ModelAttribute标记的查询条件类参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
    2. @RequestBody标记的请求体对象参数,需要同时用@Valid或@Validated标记,类中的注解校验才会生效
    3. @Valid或@Validated标记在方法或方法所属类上无效
  2. 方法中原子类型/集合类型参数校验注解的生效条件
    1. @Validated标记在方法所属类上
  3. 类A中成员是另一个类B,类B中成员校验注解生效条件
    1. 类A中的成员(即类B对象)使用@Valid修饰
  4. 按照分组启用
    1. 在注解中使用groups添加启用注解的分组
    2. 在@Validated中指定启用的分组

三、注解使用的几个注意事项

  1. 注意选传变量的默认值
    1. 选传变量没有传值时,校验会使用变量的默认值
    2. 如果有指定的默认值,会使用指定的默认值进行校验
    3. 如果没有指定默认值,那么使用类型默认值进行校验
    4. 注意默认值能否通过校验
  2. 选传变量使用默认值时,注意实例化到db时是否为null及对null值的处理(StringUtils.defaultString)
  3. 注意基本数据类型与对应包装类型之间,类型默认值的区别
  4.  注意非请求体入参中的枚举类型
    1. 枚举类型不能直接接受传参绑定
    2. 使用String或int类型先接受绑定值,再转成枚举变量
  5. 配合统一异常处理
/**
 * 与@Valid/@Validated @ModelAttribute注解配合使用,处理查询条件对象类参数中成员变量校验没有通过的异常
 * @param e
 * @return
 */
@ExceptionHandler({BindException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleBindException(BindException e) {
    LOG.warn("GlobalExceptionHandler BindException", e);
    List<ObjectError> allErrors = e.getAllErrors();
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), allErrors.get(0).getDefaultMessage());
}
 
/**
 * 与@Valid/@Validated @RequestBody配合使用,处理请求体对象类参数中成员变量校验没有通过的异常
 * @param e
 * @param request
 * @return
 */
@ExceptionHandler({MethodArgumentNotValidException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request){
    LOG.warn("handleMethodArgumentNotValidException={}", request.getRequestURI(), e);
    BindingResult bindingResult = e.getBindingResult();
    if(bindingResult.hasErrors()){
        List<ObjectError> errors = bindingResult.getAllErrors();
        if (!errors.isEmpty()) {
            // 这里列出了全部错误参数,按正常逻辑,只需要第一条错误即可
            FieldError fieldError = (FieldError) errors.get(0);
            return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), fieldError.getDefaultMessage());
        }
    }
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), ErrorCodeEnum.INVALID_PARAMS.toString());
}
 
/**
 * 当@Validated修饰类时,处理接口原子类型参数没有通过校验的异常
 * @param e
 * @return
 */
@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.OK)
public Response handleConstraintViolationException(ConstraintViolationException e) {
    LOG.warn("GlobalExceptionHandler ConstraintViolationException", e);
    Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
    Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
    ConstraintViolation<?> next = iterator.next();
    return Response.fail(ErrorCodeEnum.INVALID_PARAMS.getCode(), next.getMessage());
}

四、具体场景示例

  1. 用户的id
    1. 创建时不需要传参
    2. 编辑时必须传参
    3. 数据库中,该字段属性是bigint primary not null auto_increment comment 'id';
  2. 用户的name
    1. 创建时name必传,不能是空或空白字符
    2. 编辑时name不可修改,不需要传参
    3. 数据库中,该字段属性是varchar(30) not null default '' comment '姓名';
  3. 用户的电话phone
    1. 创建时phone必传,不能是空或空白字符
    2. 编辑时phone可以修改,必传
    3. 数据库中,该字段属性是char(11) not null default '' comment '手机';
  4. 用户的地址address
    1. 无论创建、编辑,都是选传
    2. 数据库中,该字段属性是varchar(30) not null default '' comment '地址';
  5. 用户的出生年份year
    1. 无论创建、编辑,都是选传
    2. 数据库中,该字段属性是int not null default 0 comment '出生年份';
    3. 假设用户出生年份在1970-2000年之间
  6. 用户的性别gender
    1. 无论创建、编辑,都是选传
    2. 数据库中,该字段属性是tinyint not null default 0 comment '性别,1:男,2:女,0:未知';

Person代码示例

public class Person{
     
    @Min(value = 1, message = "缺少id", groups = {Update.class})
    private int id;
 
    @NotBlank(message = "缺少姓名", groups = {Insert.class})
    @Length(min = 2, max = 30, message = "姓名长度必须在2-30之间", groups = {Insert.class})
    private String name;
 
    @NotBlank(message = "缺少手机号", groups = {Insert.class, Update.class})
    @Pattern(regexp = "^1[0-9]{10}$", message = "手机号不合法", groups = {Insert.class, Update.class})
    private String phone;
 
    @Length(min = 3, max = 30, message = "地址长度必须在3-30之间", groups = {Insert.class, Update.class})
    private String address;
   
    @Range(min = 1970, max = 2000, message = "出生年份必须在1970-2000之间", groups = {Insert.class, Update.class})
    private Integer year;
 
    @ApiModelProperty(value = "性别")
    private GenderEnum genderEnum;
}

Person接口示例代码如下

@RestController
@RequestMapping("/person")
@Validated
public class PersonController extends BaseController {
 
    @Autowired
    private PersonLogic personLogic;
 
    @PostMapping("/createPerson")
    public Response<Void> createPerson(@Validated(Insert.class) @RequestBody Person person){
        personLogic.createPerson(person);
        return Response.ok();
    }
 
    @PostMapping("/editPerson")
    public Response<Void> editPerson(@Validated(Update.class) @RequestBody Person person){
        personLogic.editPerson(person);
        return Response.ok();
    }
 
    @GetMapping("/info")
    public Response<Person> getPersonById(@Min(value = 1, message = "缺少id") @RequestParam int id){
        return Response.ok().setData(personLogic.getPersonById(id));
    }
 
    @GetMapping("/listPersons")
    public Response<List<Person>> listPersons(@Valid @ModelAttribute PersonQueryParam queryParam){
        if(StringUtils.isNotBlank(queryParam.getGender())){
            queryParam.setGenderEnum(GenderEnum.findByString(queryParam.getGender()));
        }
        return Response.ok().setData(personLogic.listPersons(queryParam));
    }
}

查询条件类代码示例如下

@Data
public class PersonQueryParam {
 
    @Length(min = 1, max = 1, message = "性别错误")
    @ApiModelProperty(value = "性别,值域:[男,女]")
    private String gender;
 
    @ApiModelProperty(value = "性别", hidden = true)
    private GenderEnum genderEnum;
 
    @Length(min = 1, max = 30, message = "模糊查询姓名长度必须在1-30之间")
    private String namePattern;
}

以上

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java的自定义注解是一种给代码提供额外信息的方式,可以在运行时通过反射机制获取注解的信息。通过自定义注解,我们可以实现参数校验,提高代码的健壮性和可维护性。 首先,我们需要定义一个注解类,用于定义参数校验的规则。比如,我们可以定义一个注解叫做@ParamCheck,用于对方法的参数进行校验。 ```java @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ParamCheck { String value(); } ``` 接着,在需要进行参数校验的方法上使用@ParamCheck注解,并给注解传入校验规则的表达式。比如,我们可以给一个名为checkNumber的方法的参数添加校验注解。 ```java public void checkNumber(@ParamCheck("number > 0") int number) { // ... } ``` 然后,在方法内部,通过反射机制获取参数注解信息,并根据注解定义的校验规则对参数进行校验。 ```java public void checkNumber(int number) { Parameter parameter = ...; // 获取方法的参数信息 ParamCheck paramCheckAnnotation = parameter.getAnnotation(ParamCheck.class); if (paramCheckAnnotation != null) { String expression = paramCheckAnnotation.value(); // 根据expression对number进行校验 // ... } } ``` 最后,我们可以在调用checkNumber方法时传入一个不满足校验规则的参数,比如-10,当方法内部进行参数校验时,可以捕获到校验失败的情况,并进行相应处理。 ```java checkNumber(-10); // 参数校验失败,抛出异常或者进行其他处理 ``` 通过自定义注解实现参数校验可以方便地对代码进行统一的校验规则管理,提高代码的可维护性和可读性。同时,由于注解是在运行时通过反射获取,可以对代码进行动态改变和扩展,使得我们可以更加灵活地进行参数校验

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值