SpringBoot——关于controller参数校验
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
参数校验主要使用两个标签@Validated和@Valid
@Valid是Hibernate的注解校验@Validated是spring的是@Valid的增强这两个标签也有一些不同之处@Valid可以标注在成员属性上也可以嵌套校验而@Validated不行但是@Validated可以使用分组校验
maven导入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.5</version>
</dependency>
通常用到的注解基本都在javax.validation.constraints包下基本都有value设定值、message设置错误消息、groups指定分组属性
- @AssertFalse必须为false支持boolean和Booleannull是有效的
- @AssertTrue必须为true支持boolean和Booleannull是有效的
- @DecimalMax必须是一个小于或者小于等于设定值的数字可以用inclusive指定是否包含数字默认true并且value是String类型的支持BigDecimal、BigInteger、CharSequence、byte / short / int / long以及它们的包装类由于舍入原因不支持double和floatnull是有效的
- @DecimalMin必须是一个大于或者大于等于设定值的数字其他同@DecimalMax
- @Digits设定可接受范围内的数字必须指定integer整数位数和fraction小数位数支持BigDecimal、BigInteger、CharSequence、byte / short / int / long以及它们的包装类null是有效的
- @Email必须是一个正确格式的邮箱可以使用regexp指定正则表达式默认是任意字符串可以使用flags指定正则表达式的选项null是有效的
- @Future必须是一个未来的瞬间、日期或时间Now是虚拟机默认的当前时区的时间支持java.util.Date、java.util.Calendar、java.time.Instant、java.time.LocalDate、java.time.LocalDateTime、java.time.LocalTime、java.time.MonthDay、java.time.OffsetDateTime、java.time.OffsetTime、java.time.Year、java.time.YearMonth、java.time.ZonedDateTime、java.time.chrono.HijrahDate、java.time.chrono.JapaneseDate、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDatenull是有效的
- @FutureOrPresent必须是现在或未来的瞬间、日期或时间其他同@Future
- @Max必须是小于等于指定值的数字value是long类型支持BigDecimal、BigInteger、byte / short / int / long以及它们的包装类不支持float和doublenull是有效的
- @Min必须是大于等于指定值的数字其他同@Max
- @Negative必须是一个严格的负数0为无效值支持BigDecimal、BigInteger、byte / short / int / long / float / double以及它们的包装类null是有效的
- @NegativeOrZero必须是负数或者0其他同@Negative
- @NotBlank不能为null并且至少包含一个非空白字符接受CharSequence
- @NotEmpty不能为null或空集合支持CharSequence字符序列长度、Collection集合size、Mapmap size、Array数组长度
- @NotNull不能为null支持所有类型
- @Null必须为null支持所有类型
- @Past必须是一个过去的瞬间、日期或时间其他同@Future
- @PastOrPresent必须是现在或过去的瞬间、日期或时间其他同@Future
- @Pattern必须符合指定的正则表达式必须使用regexp参数指定正则表达式
- @Positive必须是一个严格的正数0为无效值其他同@Negative
- @PositiveOrZero必须是正数或者0其他同@Negative
- @Size指定元素大小必须在指定范围内包括边界值使用min指定下边界默认0使用max指定上边界默认Integer.MAX_VALUE支持CharSequence、Collection、Map、Arraynull是有效的
单参数校验
在controller类上添加@Validated标签在方法的参数前加验证标签并且同一个参数可以添加多个标签
启动类使用默认配置端口8080
/**
* 2022年12月2日下午4:00:48
*/
package testspringboot.test6paramvalidation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author XWF
*
*/
@SpringBootApplication
public class Test6Main {
/**
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(Test6Main.class, args);
}
}
controller类
/**
* 2022年12月2日下午4:05:34
*/
package testspringboot.test6paramvalidation;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author XWF
*
*/
@RestController
@RequestMapping("/test6")
@Validated
public class Test6Controller {
@RequestMapping("/a")
public String a(@NotNull(message = "参数s不能为null") String s, @Min(5) @Max(value = 10) long a) {
System.out.println(s);
System.out.println(a);
return String.format("s:%s a:%d", s, a);
}
}
postman测试
返回的错误msg也可以使用自定义设定使用@RestControllerAdvice注释一个类然后在类里定义各种错误msg就像这样
package testspringboot.test6paramvalidation;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ValidException {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public String handleValidException(MethodArgumentNotValidException e) {
List<String> msgList = e.getBindingResult().getAllErrors()
.stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
return "MethodArgumentNotValidException: " + msgList.toString();
}
@ExceptionHandler(value = ConstraintViolationException.class)
public String handleConstraintViolationException(ConstraintViolationException e) {
List<String> msgList = e.getConstraintViolations()
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
return "ConstraintViolationException: " + msgList.toString();//返回错误描述
}
}
再次测试结果
默认验证所有参数即使前面验证不通过也会继续验证可以设置快速失败使验证失败立即返回不继续验证
自定义注入Validator类
package testspringboot.test6paramvalidation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ValidatConfig {
@Bean
public Validator validator() {
ValidatorFactory vfactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)//开启快速失败
.buildValidatorFactory();
return vfactory.getValidator();
}
}
再次测试结果只显示一个错误msg了
实体类校验
只需要在controller类的方法实体类参数前加@Validated或者@Valid标签实体类里的属性前加验证标签controller类上可以不用@Validated标签也行
实体类
package testspringboot.test6paramvalidation;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
public class Bparam {
@NotNull
public String s;
@Max(value = 10, message = "Bparam的x参数不能超过10")
public int x;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
@Override
public String toString() {
return "Bparam [s=" + s + ", x=" + x + "]";
}
}
controller类里的方法
@RequestMapping("/b")
public String b(@Valid Bparam b) {
return b.toString();
}
测试
另外错误消息也可以在controller类的方法参数里接收参数里使用BindingResult就可以处理
@RequestMapping("/b")
public String b(@Valid Bparam b, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return b.toString();
}
使用post的消息体接收参数也一样在参数前多加一个@RequestBody
@RequestMapping("/b")
public String b(@RequestBody @Validated Bparam b, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return b.toString();
}
分组校验
可以为同一属性设置不同情况下应用不同的注解标签需要在注解标签里使用groups参数groups是一个class集合一个标签可以设置多个group在controller类里方法的参数前的@Validated标签里使用value指定要使用的group验证可以指定多个group验证没有设置groups的标签默认属于Default.class的group设置group的class通常使用interface可以写在外面或者直接写到实体类内部
实体类
/**
* 2023年1月13日上午11:08:47
*/
package testspringboot.test6paramvalidation;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
/**
* @author XWF
*
*/
public class Cparam {
@AssertTrue(message = "b应为true", groups = CparamBTrue.class)
@AssertFalse(message = "b应为false", groups = CparamBFalse.class)
public boolean b;
@NotBlank
@Size(min = 1, max = 5, message = "s的长度1~5")
public String s;
@Min(20)
public int i;
// interface CparamBTrue{}
// interface CparamBFalse{}
}
interface CparamBTrue{}
interface CparamBFalse{}
controller方法
@RequestMapping("/c1")
public String c1(@RequestBody @Validated(CparamBTrue.class) Cparam c, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return "OK";
}
@RequestMapping("/c2")
public String c2(@RequestBody @Validated(value = {CparamBFalse.class, Default.class}) Cparam c, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return "OK";
}
分组测试
c1只验证了group是CparamBTrue的成员bc2除了验证了group是CparamBFalse的成员b也验证了没有设置groups的s和i
另外也可以设置动态组校验根据某些条件和情况设置验证的groups需要在实体类上添加@GroupSequenceProvider标签指定实现了DefaultGroupSequenceProvider接口并实现接口里getValidationGroups方法的classgetValidationGroups方法返回List<Class<?>>即为当前请求需要使用的groups返回值相当于controller类方法参数前@Validated标签里的value的作用
例如根据实体类内boolean值指定int值使用正负数
实体类
/**
* 2023年1月13日下午3:03:56
*/
package testspringboot.test6paramvalidation;
import javax.validation.constraints.Negative;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import org.hibernate.validator.group.GroupSequenceProvider;
/**
* @author XWF
*
*/
@GroupSequenceProvider(value = C3paramGroupProvider.class)
public class C3param {
@NotNull(message = "b不能为null")
public boolean b;
@NotNull
@Positive(message = "b为true时i应大于0", groups = BTrue.class)
@Negative(message = "b为false时i应小于0", groups = BFalse.class)
public int i;
@Override
public String toString() {
return "C3param [b=" + b + ", i=" + i + "]";
}
interface BTrue{}
interface BFalse{}
}
GroupProvider
/**
* 2023年1月13日下午3:10:32
*/
package testspringboot.test6paramvalidation;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
/**
* @author XWF
*
*/
public class C3paramGroupProvider implements DefaultGroupSequenceProvider<C3param> {
@Override
public List<Class<?>> getValidationGroups(C3param object) {
System.out.println("obj:" + object);
List<Class<?>> groupList = new ArrayList<>();
groupList.add(C3param.class);//实体类需要加入
if (object != null) {//该方法会调用多次object可能为null
//b为true时使用BTrue.class组b为false时使用BFalse.class组
groupList.add(object.b ? C3param.BTrue.class : C3param.BFalse.class);
}
return groupList;
}
}
controller方法
@RequestMapping("/c3")
public String c3(@RequestBody @Validated C3param c3, BindingResult result) {
System.out.println("param:" + c3);
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return "OK";
}
测试
嵌套校验
实体类成员为另一个级联的类时需要在成员前使用@Valid标签支持嵌套并且提供该级联的成员属性的get方法另外级联类内部也需要提供需要验证的成员属性的get方法不验证的成员不用get方法
第一层实体类
/**
* 2023年1月13日下午3:27:12
*/
package testspringboot.test6paramvalidation;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
/**
* @author XWF
*
*/
public class D1param {
@AssertTrue(message = "D1param.b必须为true")
public boolean b;
@Valid
public D2param d2;
public D2param getD2() {//级联对象需要get方法
return d2;
}
}
第二层实体类
/**
* 2023年1月13日下午3:27:28
*/
package testspringboot.test6paramvalidation;
import javax.validation.Valid;
import javax.validation.constraints.Positive;
/**
* @author XWF
*
*/
public class D2param {
@Positive(message = "D2param.i必须为正数")
public int i;
public String s;//不验证不需get方法
@Valid
public D3param d3;
public int getI() {//成员需要get方法
return i;
}
public D3param getD3() {//级联对象需要get方法
return d3;
}
}
第三层实体类
/**
* 2023年1月13日下午3:37:33
*/
package testspringboot.test6paramvalidation;
import javax.validation.constraints.NotNull;
/**
* @author XWF
*
*/
public class D3param {
@NotNull(message = "D3param.s不能为null")
public String s;
public String getS() {//成员需要get方法
return s;
}
}
controller方法
@RequestMapping("/d")
public String d(@RequestBody @Validated D1param d1, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return "OK";
}
测试
自定义注解
定义一个注解使用@Retention、@Target、@Constraint标签注释并携带三个方法message()、groups()、payload()并在@Constraint标签里使用validatedBy属性指定自定义验证类自定义验证类实现ConstraintValidator<A extends Annotation, T>接口的boolean isValid(T value, ConstraintValidatorContext context)方法判断验证是否通过
自定义注解功能验证是偶数
/**
* 2023年1月13日下午4:24:06
*/
package testspringboot.test6paramvalidation;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Retention(RUNTIME)
@Target(FIELD)
@Constraint(validatedBy = EValidator.class)
/**
* @author XWF
*
*/
public @interface EAnnotation {
String message() default "应该是偶数";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
对应的验证类
/**
* 2023年1月13日下午4:25:39
*/
package testspringboot.test6paramvalidation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @author XWF
*
*/
public class EValidator implements ConstraintValidator<EAnnotation, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value % 2 == 0;
}
}
实体类
/**
* 2023年1月13日下午3:52:20
*/
package testspringboot.test6paramvalidation;
/**
* @author XWF
*
*/
public class Eparam {
@EAnnotation
public int i;
}
controller方法
@RequestMapping("/e")
public String e(@RequestBody @Validated Eparam e, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList());
return "BindingResult Errors: " + errors.toString();
}
return "OK";
}
测试