1. 前言
在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用if ... else ...
手动进行参数验证。当参数个数大于3个时,使用if ... else ...
进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。
在Java中,注解(Annotation)是一种代码标记,通常用于提供元数据,这些元数据可以被编译器或运行时环境使用。这些注解通常用于框架和库中,以实现更加灵活和可配置的代码。
2. 常用注解描述
@NotNull
- 描述:标记一个值不能为null。
- 示例:
public class User {@NotNullprivate String name;// ...}
@NotEmpty
- 描述:标记一个集合(如List、Set等)不能为空。
- 示例:
public class User {@NotEmptyprivate List<String> interests;// ...}
@NotBlank
- 描述:标记一个字符串不能为空白(即null、空字符串或只包含空格)。
- 示例:
public class User {@NotBlankprivate String username;// ...}
@Size
- 描述:标记一个字符串或集合的大小必须在指定的范围内。
- 示例:
public class User {@Size(min = 2, max = 50)private String username;// ...}
@Min 和 @Max
- 描述:标记一个数值必须在指定的最小值和最大值之间。
- 示例:
public class User {@Min(18)@Max(60)private int age;// ...}
@DecimalMin 和 @DecimalMax
- 描述:标记一个浮点数或双精度数必须在指定的最小值和最大值之间。
- 示例:
public class User {@DecimalMin("0.01") @DecimalMax("100.00")private double discount;// ...}
@Digits
- 描述:标记一个整数或浮点数必须在指定的精度和总数值范围内。
- 示例:
public class User {@Digits(integer = 3, fraction = 2) // 总长度为5,3位整数,2位小数。例如:"123.45" 是合法的。private BigDecimal amount;// ...}
@Pattern
- 描述:标记一个字符串必须匹配指定的正则表达式。通常用于验证输入格式。例如电子邮件地址、电话号码格式等。@Pattern注解在javax.validation.constraints包中。@Pattern(regexp = “^\w{5,}$”)表示长度在5-20之间,由字母、数字、下划线组成的字符串。@Pattern注解用于类字段上,例如用户密码字段。
- 示例:
public class User { @Pattern(regexp = "^[a-zA-Z0-9]*$") private String password; //... }
@Email
- 描述:标记一个字符串必须是一个有效的电子邮件地址。
- 示例:
@Emailprivate String emailAddress;
@AssertTrue 和 @AssertFalse
- 描述:标记一个布尔值必须为true或false。
- 示例:
@AssertTrueprivate boolean isValid;@AssertFalseprivate boolean isNotValid;
@Future
- 描述:标记一个日期必须是在未来某个时间点之后。
- 示例:
@Futureprivate Date expiryDate;
@Past
- 描述:标记一个日期必须是在过去某个时间点之前。
- 示例:
@Pastprivate Date purchaseDate;
这些注解通常与验证框架(如Hibernate Validator)一起使用,以在运行时验证对象的属性。
3. 注解使用
- 在项目的
pom.xml
文件中添加如下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
在实体类中使用上述注解,代码如下:
package com.yyqq.demo.entity;import lombok.Data;import javax.validation.constraints.*;@Datapublic class User {@NotBlank(message = "用户姓名不能为空")private String name;@NotBlank(message = "密码不能为空")@Size(min = 6, message = "密码长度不能少于6位")private String password;@Min(value = 0, message = "年龄不能小于0岁")@Max(value = 150, message = "年龄不应超过150岁")private Integer age;@Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\d{8}$", message = "手机号格式不正确")private String phone;}
控制器类使用验证,代码如下:
import com.yyqq.demo.util.Result;import com.yyqq.demo.entity.User;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;@RestController@RequestMapping("/user")public class UserController {@PostMapping("/add")public Result add(@Valid @RequestBody User user) { return Result.success(user);}}
Result是封装结果的一个类,用于返回统一的结果,代码如下:
package com.yyqq.demo.util;import lombok.Data;import java.io.Serializable;@Datapublic class Result<T> implements Serializable {private int code;private boolean success;private T data;private String msg;private Result(int code, T data, String msg) {this.code = code;this.data = data;this.msg = msg;this.success = code == 200;}public static <T> Result<T> sucess(T data) {return new Result<>(200, data, null);}public static <T> Result<T> fail(String msg) {return new Result<>(500, null, msg);}}
定义全局异常处理类,我们在全局异常处理类中使用
ExceptionHandler
捕获BindException
异常,获取参数验证异常信息,最后返回统一的异常结果格式,代码如下:package com.yyqq.demo.util;import com.yyqq.demo.util.Result;import org.springframework.validation.BindException;import org.springframework.validation.BindingResult;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(BindException.class)public Result handleError(BindException e) {BindingResult bindingResult = e.getBindingResult();return Result.fail(bindingResult.getFieldError().getDefaultMessage());}}
4. 使用分组验证
- 用于插入记录时的分组验证,代码如下:
package com.yyqq.demo.interceptor;import javax.validation.groups.Default;public interface Insert extends Default {}
- 用于更新记录时的分组验证,代码如下:
package com.yyqq.demo.interceptor;import javax.validation.groups.Default;public interface Update extends Default {}
- 在实体类中进行分组标记,代码如下:
package com.yyqq.demo.entity;import lombok.Data;import javax.validation.constraints.*;@Datapublic class User {@NotBlank(groups = {Insert.class, Update.class})@NotBlank(message = "用户姓名不能为空")private String name;@NotBlank(message = "密码不能为空")@Size(min = 6, message = "密码长度不能少于6位")private String password;@Min(value = 0, message = "年龄不能小于0岁")@Max(value = 150, message = "年龄不应超过150岁")private Integer age;@NotBlank(groups = {Insert.class, Update.class})@Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$", message = "手机号格式不正确")private String phone;}
- 控制器类使用分组验证
package com.yyqq.demo.controller;import com.yyqq.demo.util.Result;import com.yyqq.demo.entity.User;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;@RestController@RequestMapping("/user")public class UserController {@PostMapping("/add")public Result add(@Validated(Insert.class) @RequestBody User user) { return Result.success(user);}@PostMapping("/update")public Result update(@Validated(Update.class) @RequestBody User user) { return Result.success(user);}}
5. 自定义验证注解
除了框架自带的注解,平时的工作中可能需要我们自定义验证注解处理特定的业务需求。
- 定义注解
package com.yyqq.demo.validate;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@Retention(RUNTIME)@Constraint(validatedBy = {PhoneValidator.class})@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})public @interface Phone {String message() default "手机号格式错误";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
- 定义验证器类
package com.yyqq.demo.validate;import javax.validation.ConstraintValidatorContext;import javax.validation.ConstraintValidator;import java.util.regex.Pattern;public class PhoneValidator implements ConstraintValidator<Phone, String> {private static final String REGEX = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$";@Overridepublic boolean isValid(String s, ConstraintValidatorContext context) {boolean result = false;try {result = Pattern.matches(REGEX, s);} catch (Exception e) {System.out.println("验证手机号格式时发生异常,异常信息:" + e);}return result;}}
- 实体类使用注解
package com.yyqq.demo.validate;public class User {//其他属性...//@Pattern(regexp = "^((13[0-9])|(15[^4])|(18[0-9])|(17[0-9])|(147))\\d{8}$", message = "手机号格式不正确")@Phoneprivate String phone;}
6. @Valid与@Validated的区别
用于参数校验的注解通常有两个:@Valid
和@Validated
。它们的区别有如下几点:
区别 | @Valid | @Validated |
---|---|---|
来源 | @Valid 是Java标准注解 | @Validated 是Spring框架定义的注解。 |
是否支持分组验证 | 不支持 | 支持 |
使用位置 | 构造函数、方法、方法参数、成员属性 | 类、方法、方法参数,不能用于成员属性 |
是否支持嵌套校验 | 支持 | 不支持 |