文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

关于SpringBoot中controller参数校验的使用

2023-01-15 09:00

关注

参数校验主要使用两个标签@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(指定分组)属性:

单参数校验

在controller类上添加@Validated标签,在方法的参数前加验证标签,并且同一个参数可以添加多个标签;

启动类:(使用默认配置,端口8080)


package testspringboot.test6paramvalidation;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 

@SpringBootApplication
public class Test6Main {
 
	
	public static void main(String[] args) {
		SpringApplication.run(Test6Main.class, args);
		
	}
 
}

 controller类:


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;
 

@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,可以写在外面或者直接写到实体类内部;

实体类:


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;
 

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的成员b,c2除了验证了group是CparamBFalse的成员b,也验证了没有设置groups的s和i;

另外也可以设置动态组校验,根据某些条件和情况设置验证的groups,需要在实体类上添加@GroupSequenceProvider标签指定实现了DefaultGroupSequenceProvider接口并实现接口里getValidationGroups方法的class,getValidationGroups方法返回List<Class<?>>,即为当前请求需要使用的groups(返回值相当于controller类方法参数前@Validated标签里的value的作用);

例如根据实体类内boolean值指定int值使用正负数:

实体类:


package testspringboot.test6paramvalidation;
 
import javax.validation.constraints.Negative;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
 
import org.hibernate.validator.group.GroupSequenceProvider;
 

@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:


package testspringboot.test6paramvalidation;
 
import java.util.ArrayList;
import java.util.List;
 
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
 

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方法)

第一层实体类:


package testspringboot.test6paramvalidation;
 
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
 

public class D1param {
 
	@AssertTrue(message = "D1param.b必须为true")
	public boolean b;
	
	@Valid
	public D2param d2;
	
	public D2param getD2() {//级联对象需要get方法
		return d2;
	}
	
}

第二层实体类:


package testspringboot.test6paramvalidation;
 
import javax.validation.Valid;
import javax.validation.constraints.Positive;
 

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;
	}
 
}

第三层实体类:


package testspringboot.test6paramvalidation;
 
import javax.validation.constraints.NotNull;
 

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)方法,判断验证是否通过;

自定义注解:(功能:验证是偶数)


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)

public @interface EAnnotation {
 
	String message() default "应该是偶数";
	
	Class<?>[] groups() default {};
	
	Class<? extends Payload>[] payload() default {};
	
}

对应的验证类:


package testspringboot.test6paramvalidation;
 
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
 

public class EValidator implements ConstraintValidator<EAnnotation, Integer> {
 
	@Override
	public boolean isValid(Integer value, ConstraintValidatorContext context) {
		return value % 2 == 0;
	}
 
}

实体类:


package testspringboot.test6paramvalidation;
 

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";
	}

测试:

 到此这篇关于关于SpringBoot中controller参数校验的使用的文章就介绍到这了,更多相关SpringBoot controller参数校验内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯