在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用if ... else ...
手动进行参数验证。当参数个数大于3个时,使用if ... else ...
进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。
在Java中,注解(Annotation)是一种代码标记,通常用于提供元数据,这些元数据可以被编译器或运行时环境使用。这些注解通常用于框架和库中,以实现更加灵活和可配置的代码。
@NotNull
public class User {
@NotNull
private String name;
// ...
}
@NotEmpty
public class User {
@NotEmpty
private List<String> interests;
// ...
}
@NotBlank
public class User {
@NotBlank
private 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
public class User {
@Pattern(regexp = "^[a-zA-Z0-9]*$")
private String password; //...
}
@Email
private String emailAddress;
@AssertTrue 和 @AssertFalse
@AssertTrue
private boolean isValid;
@AssertFalse
private boolean isNotValid;
@Future
@Future
private Date expiryDate;
@Past
@Past
private Date purchaseDate;
这些注解通常与验证框架(如Hibernate Validator)一起使用,以在运行时验证对象的属性。
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.*;
@Data
public 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;
@Data
public 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;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public Result handleError(BindException e) {
BindingResult bindingResult = e.getBindingResult();
return Result.fail(bindingResult.getFieldError().getDefaultMessage());
}
}
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.*;
@Data
public 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);
}
}
除了框架自带的注解,平时的工作中可能需要我们自定义验证注解处理特定的业务需求。
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}$";
@Override
public 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 = "手机号格式不正确")
@Phone
private String phone;
}
用于参数校验的注解通常有两个:@Valid
和@Validated
。它们的区别有如下几点:
区别 | @Valid | @Validated |
---|---|---|
来源 | @Valid 是Java标准注解 | @Validated 是Spring框架定义的注解。 |
是否支持分组验证 | 不支持 | 支持 |
使用位置 | 构造函数、方法、方法参数、成员属性 | 类、方法、方法参数,不能用于成员属性 |
是否支持嵌套校验 | 支持 | 不支持 |