在web开中,参数校验也是不可或缺的一部分,在SpringBoot中,准确说是SpringMVC中参数校验有多种方式,下面一一讲解各个方法是如何校验参数的。
直接校验
这种方法简单粗暴,直接在控制器方法中进行校验,不使用框架内置的注解。
@Controller
public class HelloController {
@ResponseBody
@GetMapping("/testValidate")
public String getUrlValue(String name, String gender) {
if (name == null || name.isEmpty()) {
return "名字不能空";
}
if (gender == null || gender.isEmpty()) {
return "性别不能空";
}
return "hello";
}
}
浏览器输入地址http://localhost:8080/testValidate?name=yxjc123,测试一下。
这种方法对于1,2个参数的校验倒是还好,如果过多不建议这样,所以推荐使用下面介绍的方法进行校验。
添加校验支持
在pom.xml文件添加校验支持spring-boot-starter-validation。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Null | 所注解的元素值为null |
@NotNull | 所注解的元素值不能为null |
@NotBlank | 所注解的元素值有内容,仅包含空格、制表符(tab键)的字符串视为空 |
@Size | 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内 |
@AssertFalse | 所注解的元素必须是Boolean类型,且值为false |
@AssertTrue | 所注解的元素必须是Boolean类型,且值为true |
@Max | 所注解的元素必须是数字,且值小于等于给定的值 |
@Min | 所注解的元素必须是数字,且值大于等于给定的值 |
@DecimalMax | 所注解的元素必须是数字,且值小于等于给定的值,通常用于BigDecimal类型 |
@DecimalMin | 所注解的元素必须是数字,且值大于等于给定的值,通常用于BigDecimal类型 |
@Digits | 所注解的元素必须是数字,且值必须是指定的位数 |
@Future | 所注解的元素必须是将来某个日期 |
@Range | 所注解的元素需在指定范围区间内 |
@Past | 所注解的元素必须是某个过去的日期 |
@PastOrPresent | 所注解的元素必须是过去某个或现在日期 |
@Pattern | 所注解的元素必须满足给定的正则表达式 |
所注解的元素需满足Email格式 |
以上是常用的校验方法。
单个参数注解校验
在SpringBoot中,Controller层可以使用这些注解对单个参数进行校验,此方法适用于参数较少的情况下。
@Controller
@Validated
public class HelloController {
@ResponseBody
@GetMapping("/testValidate")
public String getUrlValue(@NotNull(message = "名字不能空") String name,
@NotNull(message = "性别不能空") String gender) {
return "hello";
}
}
上面的例子中需要对HelloController添加注解@Validate 否则无效,浏览器中输入http://localhost:8080/testValidate?name=yxjc123,报错信息如下:后台的报错信息:
这种方式的返回结果对用户或者前端接口调用不是非常友好,需要对这些异常信息进行统一处理,请看后面的章节,SpringBoot统一异常处理。
对于参数较多的时候,这显然是不合理的,我们会在Controller层的方法中堆砌大量的校验代码,接下来介绍实体类的校验。
实体类的校验
实体类校验是将校验的方法写在实体中,控制器方法用于接受参数。
控制器代码
@Controller
@Validated
public class HelloController {
@ResponseBody
@PostMapping("/testValidate")
public String getUrlValue(@Validated User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}
return "hello";
}
}
User.java
package com.example.yxjc.domain;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 2733191978271622951L;
@NotNull(message = "名字不能空")
private String name;
@NotNull(message = "性别不能空")
@Range(min=0, max=2, message = "性别输入错误")
private String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
使用postman测试
上面的例子中如果每个方法都是用BindingResult对参数进行错误判断也是不可取的,因为这样也增加了代码的重复度,所以同样可以使用统一异常处理。
分组校验
一般情况下系统都有注册和登录功能,
在系统注册的时候需要提交用户名、密码、性别、手机号等字段,
在登录的时候只需要用户名和密码字段,
如果在没有分组校验的情况下需要写两个实体类比如:
UserLogin.class和UserReg.class,显然这一增加了系统的一些冗余代码,为了解决这个问题分组校验出现了。
它可以在一个实体类中定义两个不同的分组A和B,A分组用于登录,B分组用于注册。以下是例子:
1) 首先定义两个分组接口GroupA.class和GroupB.class
package com.example.yxjc.domain;
//登录
public interface GroupA {
}
package com.example.yxjc.domain;
//注册
public interface GroupB {
}
2) 给实体类增加不同的分组
package com.example.yxjc.domain;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 2733191978271622951L;
@NotNull(message = "名字不能空",groups = {GroupA.class, GroupB.class})
private String name;
@NotNull(message = "密码不能空",groups = {GroupA.class, GroupB.class})
private String passwd;
@NotNull(message = "手机号不能空",groups = {GroupB.class})
private String mobile;
@NotNull(message = "性别不能空",groups = {GroupB.class})
@Range(min=0, max=2, message = "性别输入错误")
private String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
3) 控制器层定义两个不同的方法login和register
@Controller
@Validated
public class HelloController {
@ResponseBody
@PostMapping("/user/login")
public String login(@Validated({GroupA.class}) User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}
return "login";
}
@ResponseBody
@PostMapping("/user/register")
public String register(@Validated({GroupB.class}) User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}
return "register";
}
}
4) 使用postman测试一下虽然两个方法使用了同一个类user.class但是,因为有分组校验的区别,所以在校验的时候它们的字段是不同的。其中
登录方法使用字段 name和passwd
注册方法使用字段 name、passwd、Mobile和gender。
自定义校验方法
虽然框架中提供的注解校验的方法已经很多了,但是有时候我们需要自定义一些通用的校验方法,比如身份证号码,这是框架中所没有的。下面介绍详细的处理过程。
1)首先,定义一个@IdCard注解
package com.example.yxjc.validate;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidate.class)
public @interface IdCard {
String message() default "身份证格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2) 定义真正的身份证校验类 IdCardValidate.class,关于身份证的判断,由于篇幅问题这里简单处理,只判断是否等于12345。
package com.example.yxjc.validate;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IdCardValidate implements ConstraintValidator<IdCard, Object> {
@Override
public boolean isValid(Object idCard, ConstraintValidatorContext constraintValidatorContext) {
//关于身份证的判断,由于篇幅问题这里简单处理,只判断是否等于12345。
return idCard.toString().equals("12345");
}
}
3) 修改实体类
package com.example.yxjc.domain;
import com.example.yxjc.validate.IdCard;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 2733191978271622951L;
@NotNull(message = "名字不能空")
private String name;
@Range(min=0, max=2, message = "性别输入错误")
private String gender;
@IdCard //身份证校验
private String idCard;
public String getName() {
return name;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
4) 修改我们的控制器类
@Controller
@Validated
public class HelloController {
@ResponseBody
@PostMapping("/user/idCard")
public String idCard(@Validated User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}
return "success";
}
}
5) 使用postman测试一下