有参数传递的地方都少不了参数校验。在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。试想一下,如果在controller层中没有经过任何校验的参数通过service层、dao层一路来到了数据库就可能导致严重的后果,最好的结果是查不出数据,严重一点就是报错,如果这些没有被校验的参数中包含了恶意代码,那就可能导致更严重的后果。
这里我们主要介绍在springboot中的几种参数校验方式。常用的用于参数校验的注解如下:
- @AssertFalse 所注解的元素必须是Boolean类型,且值为false
- @AssertTrue 所注解的元素必须是Boolean类型,且值为true
- @DecimalMax 所注解的元素必须是数字,且值小于等于给定的值
- @DecimalMin 所注解的元素必须是数字,且值大于等于给定的值
- @Digits 所注解的元素必须是数字,且值必须是指定的位数
- @Future 所注解的元素必须是将来某个日期
- @Max 所注解的元素必须是数字,且值小于等于给定的值
- @Min 所注解的元素必须是数字,且值小于等于给定的值
- @Range 所注解的元素需在指定范围区间内
- @NotNull 所注解的元素值不能为null
- @NotBlank 所注解的元素值有内容
- @Null 所注解的元素值为null
- @Past 所注解的元素必须是某个过去的日期
- @PastOrPresent 所注解的元素必须是过去某个或现在日期
- @Pattern 所注解的元素必须满足给定的正则表达式
- @Size 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内
- @Email 所注解的元素需满足Email格式
其他 Difference Between @NotNull, @NotEmpty, and @NotBlank
-
@NotNull 不能为null,但是可以为空字符串"" 一般用于Date,int类型
-
@NotEmpty 不能为null,不能为空字符串"",其本质是CharSequence, Collection, Map, or Array的size或者length不能为0 用于String类型
-
@NotBlank a constrained String is valid as long as it’s not null and the trimmed length is greater than zero
-
@NonNull
-
@NotNull 是 JSR303(Bean的校验框架)的注解,用于运行时检查一个属性是否为空,如果为空则不合法。
-
@NonNull 是JSR 305(缺陷检查框架)的注解,是告诉编译器这个域不可能为空,当代码检查有空值时会给出一个风险警告,目前这个注解只有IDEA支持。
controller.service层参数校验
在controller层的参数校验可以分为两种场景:
- 单个参数校验
- 实体类参数校验
单个参数校验
@RestController
@Validated
public class PingController {
@GetMapping("/getUser")
public String getUserStr(@NotNull(message = "name 不能为空") String name,
@Max(value = 99, message = "不能大于99岁") Integer age) {
return "name: " + name + " ,age:" + age;
}
}
注意单参数使用时,注解要加到类上才可生效
实体类参数校验
public class User {
/** id */
@NotNull(message="id不能为空")
private Long id;
/** 姓名 */
@NotBlank(message="姓名不能为空")
private String name;
/** 年龄 */
@NotNull(message="年龄不能为空")
@Max(message="年龄不能超过120岁", value = 120)
@Min(message="年龄不能小于1岁", value = 1)
private Integer age;
/** 创建时间 */
@Future
private Date createTime;
}
参数前面加@Validated注解
@RestController
@Validated
public class PingController {
@GetMapping("/getUser")
public String getUserStr(@Validated String name,@Validated Integer age) {
return "name: " + name + " ,age:" + age;
}
}
@Valid注解的三种校验方式
- 在Controller方法参数前加@Valid注解——校验不通过时直接抛异常
@PostMapping("/test1")
public Object test1(@Valid User user) {
return "操作成功!";
}
- 在Controller方法参数前加@Valid注解,参数后面定义一个BindingResult类型参数——执行时会将校验结果放进bindingResult里面,用户自行判断并处理
@PostMapping("/test2")
public Object test2(@RequestBody @Valid User user, BindingResult bindingResult) {
// 参数校验
//使用异常处理类来进行处理异常
Validator.validData(bindingResult);
if (bindingResult.hasErrors()) {
String messages = bindingResult.getAllErrors()
.stream()
.map(ObjectError::getDefaultMessage)
.reduce((m1, m2) -> m1 + ";" + m2)
.orElse("参数输入有误!");
throw new IllegalArgumentException(messages);
}
return "操作成功!";
}
注意:如果有多个参数, BindingResult bindingResult1,BindingResult bindingResult2, 命名不同
- 用户手动调用对应API执行校验——Validation.buildDefault ValidatorFactory().getValidator().validate(xxx)
/**
* 用户手动调用对应API执行校验
* @param user
* @return
*/
@PostMapping("/test3")
public Object test3(@RequestBody User user) {
// 参数校验
validate(user);
return "操作成功!";
}
private void validate(@Valid User user) {
Set<ConstraintViolation<@Valid User>> validateSet = Validation.buildDefaultValidatorFactory()
.getValidator()
.validate(user, new Class[0]);
if (!CollectionUtils.isEmpty(validateSet)) {
String messages = validateSet.stream()
.map(ConstraintViolation::getMessage)
.reduce((m1, m2) -> m1 + ";" + m2)
.orElse("参数输入有误!");
throw new IllegalArgumentException(messages);
}
}
异常处理类
public class Validator{
public static void validData(BindingResult bindingResult){
if(bindingResult.hasErrors() ){
for(ObjectError error:bindingResult.getAllErrors() ){
throw new ManageException(error.getDefaultMessage(),ManageCOnstants.SERVERERROR);
}
}
}
}
参数校验分组
在实际开发中经常会遇到这种情况:想要用一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同,而你又不想为这点不同去建个新的类接收参数。比如有一个/setUser接口不需要id参数,而/getUser接口又需要该参数,这种时候就可以使用参数分组来现。
- 定义表示组别的interface;
public interface GroupA {
//此处就是空接口
}
- 在实体类的注解中标记这个哪个组所使用的参数;
//一个实体类中可以定义多个组接口
public class UserInfo {
@NotNull( groups = {GroupA.class}, message = "id cannot be null")
private Integer id;
@NotNull(message = "username cannot be null")
private String name;
@NotNull(message = "sex cannot be null")
private String sex;
@Max(value = 99L)
private Integer age;
}
- 在@Validated中指定使用哪个组;
//其中Default为javax.validation.groups中的类,表示参数类中其他没有分组的参数,如果没有,/getUser接口的参数校验就只会有标记了GroupA的参数校验生效。
//但是Default我不知道有什么用,加不加无所谓
@RestController
public class PingController {
@PostMapping("/getUser")
public String getUserStr(@Validated({GroupA.class, Default.class}) UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}
@PostMapping("/setUser")
public String setUser(@Validated UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}