参数校验
参考博客链接:
https://blog.csdn.net/weixin_43376349/article/details/106065757?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_default&utm_relevant_index=2
https://www.cnblogs.com/mooba/p/11276062.html
导入依赖:
<!--参数校验引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
接下来来进行相应的操作即可。
1、效验RequestParam
比如验证用户名不能为空,首先编写相关的校验规则,如@NotBlank
,在message
中填入验证不通过时的提示语;最后,在类上增加@Validated
注解,使之生效。
代码如下所示:
@RestController
@Validated
public class UserController {
@GetMapping("/getUser")
public String getUserStr(
@Max(value = 99, message = "不能大于99岁") Integer age,
@Max(value = 99, message = "不能大于99") Integer num,
// 对于发送过来的字符串默认使用的是"",不会为空
@NotBlank(message = "name 不能为空") String name) {
return "name: " + name + " ,age:" + age;
}
}
注意: @Validated 注解添加的位置,如果添加到了方法参数上将不会生效!
可以校验多个参数:
不能大于99
name 不能为空
2、封装到实体类
但是参数大于等三个之后,建议使用类来对参数来进行封装。
@PostMapping("/add1")
public String add( @RequestBody @Validated UserDto dto) {
return "SUCCESS";
}
对应的实体类是:
@Data
public class UserDto implements Serializable {
private static final long serialVersionUID = 5354063703280604542L;
@NotNull(message = "用户名不能为空")
@Length(min = 4, max = 10, message = "用户名长度只能为4至10位")
private String username;
@NotNull(message = "密码不能为空")
@Length(min = 6, max = 14, message = "密码长度只能为6至14位")
private String password;
@NotNull(message = "启用状态不能为空")
private Boolean enable;
}
注意:这里需要将@Validated注解放到参数位置上,也是现在使用的方式。
3、使用全局异常处理器来捕捉对应的异常
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ValidationException exception) {
if(exception instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
//打印验证不通过的信息
System.out.println(item.getMessage());
}
}
return "bad request" ;
}
}
当参数校验异常的时候,该统一异常处理类在控制台打印信息的同时把bad request的字符串和HttpStatus.BAD_REQUEST
所表示的状态码400
返回给调用方(用@ResponseBody
注解实现,表示该方法的返回结果直接写入HTTP response body 中)。其中:
@ControllerAdvice
:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。@ExceptionHandler
:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法,此例中处理ValidationException
异常。
对应的响应消息:
不能大于99
name 不能为空
2022-01-24 18:07:04.200 WARN 51408 --- [nio-8080-exec-4] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [javax.validation.ConstraintViolationException: getUserStr.num: 不能大于99, getUserStr.name: name 不能为空]
在上面的例子中,我们使用BindingResult
验证不通过的结果集合,但是通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。这就涉及到两种校验模式的配置:
4、校验模式
- 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
- 快速失败模式: 只要有一个验证失败,则返回
如果想要配置第二种模式,需要添加如下配置类
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidatorConf {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
5、参数校验分组
在实际开发中经常会遇到这种情况:想要用一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同,而你又不想为这点不同去建个新的类接收参数。比如有一个/setUser
接口不需要id
参数,而/getUser
接口又需要该参数,这种时候就可以使用参数分组来实现。
- 定义表示组别的
interface
;
public interface GroupA {
}
public interface GroupB {
}
2.在@Validated
中指定使用哪个组;
@RestController
@Validated
public class UserController {
@PostMapping("/getUser")
public String getUserStr(@RequestBody @Validated({GroupA.class}) UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}
}
=====================================================================================================================================
@RestController
@RequestMapping("hello")
public class HelloController {
@PostMapping("getUser")
public String getUserStr(@RequestBody @Validated({GroupB.class}) UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}
// 统一异常来进行对应的参数校验,会将参数异常抛出到GlobalExceptionHandler
private void validData(BindingResult bindingResult) {
// 获取得到绑定的错误,然后将所有的错误绑定追加提取起来,像上面抛出
if (bindingResult.hasErrors()) {
StringBuffer sb = new StringBuffer();
for (ObjectError error : bindingResult.getAllErrors()) {
sb.append(error.getDefaultMessage());
}
throw new ValidationException(sb.toString());
}
}
}
其中Default
为javax.validation.groups
中的类,表示参数类中其他没有分组的参数;如果没有,/getUser
接口的参数校验就只会有标记了GroupA
的参数校验生效。
看一下对应的DTO:
@Data
public class UserInfo {
/**
* A组才会来做参数校验的字段
*/
@NotBlank( groups = {GroupA.class}, message = "id cannot be null")
private Integer id;
/**
* B组才会来做参数校验
*/
@NotBlank(groups = {GroupB.class},message = "username cannot be null")
private String name;
@NotBlank(message = "sex cannot be null")
private String sex;
@Max(value = 99L)
private Integer age;
}
上面的controller层中的代码就意味着,校验A组合B组的标注了的属性;
如:在HelloController只会去校验GroupB组标注了的参数,而不会去校验其他的参数;在UserController只会取校验GroupA组标注了的参数,而不会去标注其他的参数。
对应的测试类如下所示:
测试B组参数校验:
{
"id":null,
"name":"liguang",
"sex":null,
"age":null
}
测试A组参数校验:
{
"id":11,
"name":null,
"sex":null,
"age":null
}
上面两种测试可以看到都是正常的。
但是还有一种情况,就是想如果没有标注了的,也需要来做参数校验,那么需要按照下面中使用方式来进行操作。
3.在实体类的注解中标记这个哪个组所使用的参数;
@Data
public class UserInfo {
// A组才会使用到的参数校验属性
@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标注了value属性后,实体类中未标注groups属性的字段校验将会失效。示例如下:
// 自定义接口
public interface Update {
}
// 实体类
public class Demo {
@NotNull(groups = Update.class)
private Integer id;
@NotBlank
private String name;
}
// Controller 方法
public Object test(@RequestBody @Validated(Update.class)) {
...
}
以上Demo
类的id
字段会被校验而name
字段则不会处理。
原因:
校验属性默认的groups为Default.class
,该类位于javax.validation.groups
包下。如果自定义了接口,则只会处理该被接口标注的字段。
解决方法:
自定义接口继承Default
类即可。
import javax.validation.groups.Default;
// 自定义接口
public interface Update extends Default{
}
那么即使在上面的name没有标注,那么也会来进行校验。
但是二者结合使用的话,我感觉还不如全部进行校验。
6、级联参数校验
当参数bean中的属性又是一个复杂数据类型或者是一个集合的时候,如果需要对其进行进一步的校验需要考虑哪些情况呢?
@Data
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;
@NotEmpty
private List<Parent> parents;
}
比如对于parents
参数,@NotEmpty
只能保证list不为空,但是list中的元素是否为空、User对象中的属性是否合格,还需要进一步的校验。这个时候我们可以这样写:
@NotEmpty
private List<@NotNull @Valid UserInfo> parents;
然后再继续在UserInfo
类中使用注解对每个参数进行校验。
但是我们再回过头来看看,在controller中对实体类进行校验的时候使用的@Validated
,在这里只能使用@Valid
,否则会报错。关于这两个注解的具体区别可以参考@Valid 和@Validated的关系,但是在这里我想说的是使用@Valid
就没办法对UserInfo
进行分组校验。这种时候我们就会想,如果能够定义自己的validator就好了,最好能支持分组,像函数一样调用对目标参数进行校验,就像下面的validObject
方法一样:
import javax.validation.Validator;
@RestController
public class PingController {
@Autowired
private Validator validator;
@PostMapping("/setUser")
public String setUser(@RequestBody @Validated UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
Parent parent = user.getParent();
validObject(parent, validator, GroupB.class, Default.class);
return "name: " + user.getName() + ", age:" + user.getAge();
}
private void validData(BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
StringBuffer sb = new StringBuffer();
for (ObjectError error : bindingResult.getAllErrors()) {
sb.append(error.getDefaultMessage());
}
throw new ValidationException(sb.toString());
}
}
/**
* 实体类参数有效性验证
* @param bean 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回true;验证失败:将错误信息添加到message中
*/
public void validObject(Object bean, Validator validator, Class<?> ...groups) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(bean, groups);
if (!constraintViolationSet.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation violation: constraintViolationSet) {
sb.append(violation.getMessage());
}
throw new ValidationException(sb.toString());
}
}
}
@Data
public class Parent {
@NotEmpty(message = "parent name cannot be empty", groups = {GroupB.class})
private String name;
@Email(message = "should be email format")
private String email;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?