SpringBoot2.x 参数校验问题小结

作者:RtxTitanV 时间:2023-05-22 02:21:22 

本文主要对SpringBoot2.x参数校验进行简单总结,其中SpringBoot使用的2.4.5版本。

一、引入依赖


<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- lombok插件 -->
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.18.8</version>
</dependency>

二、实体类

User类:


package com.rtxtitanv.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.User
* @description 用户实体类
* @date 2021/8/15 16:28
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
   @NotNull(message = "id不能为空")
   private Long id;
   @Length(min = 6, max = 20, message = "用户名长度不小于6,不超过20")
   @NotNull(message = "用户名不能为空")
   private String username;
   @Pattern(regexp = "^[A-Z][A-Za-z0-9_]{5,19}$", message = "密码以大写英文字母开头,只包含英文字母、数字、下划线,长度在6到20之间")
   @NotNull(message = "密码不能为空")
   private String password;
   @Max(value = 60, message = "年龄最大为60")
   @Min(value = 18, message = "年龄最小为18")
   @NotNull(message = "年龄不能为空")
   private Integer age;
   @Email(message = "邮箱格式不正确")
   @NotEmpty(message = "邮箱不能为空")
   private String email;
   private String rank;
}

通用响应类:


package com.rtxtitanv.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

import java.io.Serializable;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.CommonResult
* @description 通用响应类
* @date 2021/8/15 17:35
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T> implements Serializable {

private static final long serialVersionUID = 5231430760082814286L;
   private int code;
   private String message;
   private T data;

public static <T> CommonResult<T> ok(String message, T data) {
       return new CommonResult<T>(HttpStatus.OK.value(), message, data);
   }

public static <T> CommonResult<T> fail(int code, String message, T data) {
       return new CommonResult<>(code, message, data);
   }
}

三、常用的校验注解

这里对一些用于参数校验的常用注解进行总结:

  • @Null:必须为null。

  • @NotNull:必须不为null。

  • @AssertTrue:必须为true。

  • @AssertFalse:必须为false。

  • @Min(value):必须是一个大于等于指定值的数字。

  • @Max(value):必须是一个小于等于指定值的数字。

  • @DecimalMin(value):必须是一个大于等于指定值的数字。

  • @DecimalMax(value):必须是一个小于等于指定值的数字。

  • @Size(min=,max=):大小必须在指定的范围内。

  • @Digits(integer, fraction):必须是一个数字,其值必须在可接受的范围内。

  • @Past:必须是一个过去的日期。

  • @Future:必须是一个将来的日期。

  • @Pattern(regex=):必须符合指定的正则表达式。

  • @Email:必须是一个有效的email地址。

  • @Length(min=,max=):字符串长度是否在指定范围内。

  • @NotBlank:必须非空且长度大于0。

  • @NotEmpty:必须不为null或空。

  • @URL(protocol=,host,port):必须是一个有效的URL,如果提供了protocol,host等,则还需满足提供的条件。

四、校验Controller中的参数

1.校验请求体

创建UserController,在需要校验的参数上添加@Valid注解:


package com.rtxtitanv.controller;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.controller.UserController
* @description UserController
* @date 2021/8/15 16:33
*/
@RequestMapping("/user")
@RestController
public class UserController {

@PostMapping("/save")
   public CommonResult<User> saveUser(@RequestBody @Valid User user) {
       return CommonResult.ok("保存用户成功", user);
   }
}

启动项目,发送如下POST请求,请求地址为http://localhost:8080/user/save

SpringBoot2.x 参数校验问题小结

如果验证失败会抛出MethodArgumentNotValidException,默认情况下,Spring会将MethodArgumentNotValidException异常转换为HTTP Status 400。查看控制台打印的日志发现抛出了MethodArgumentNotValidException

SpringBoot2.x 参数校验问题小结

创建全局异常处理类捕获异常并进行处理:


package com.rtxtitanv.handler;

import com.rtxtitanv.model.CommonResult;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.handler.GlobalExceptionHandler
* @description 全局异常处理类
* @date 2021/8/15 16:36
*/
@RestControllerAdvice(annotations = {Controller.class, RestController.class})
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
   @ResponseStatus(code = HttpStatus.BAD_REQUEST)
   public CommonResult<Map<String, String>> validateException(MethodArgumentNotValidException e) {
       Map<String, String> errors = new HashMap<>(16);
       e.getBindingResult().getAllErrors()
           .forEach(error -> errors.put(((FieldError)error).getField(), error.getDefaultMessage()));
       return CommonResult.fail(HttpStatus.BAD_REQUEST.value(), "无效的参数", errors);
   }
}

重启项目后再次发送如下POST请求:

SpringBoot2.x 参数校验问题小结

2.校验请求参数

UserController上添加Validated注解并新增以下方法:


@GetMapping("/get/{id}")
public CommonResult<User>
   getUserById(@Valid @PathVariable(value = "id") @Min(value = 1, message = "id不能小于1") Long id) {
   User user = new User(id, "ZhaoYun", "A123456sd", 20, "zhaoyun123@xxx.com", "黄金");
   return CommonResult.ok("根据id查询用户成功", user);
}

@DeleteMapping("/delete")
public CommonResult<User> deleteByUsername(
   @Valid @RequestParam(value = "username") @Size(min = 6, max = 20, message = "用户名长度不在指定范围内") String username) {
   User user = new User(1L, username, "A123456sd", 20, "zhaoyun123@xxx.com", "黄金");
   return CommonResult.ok("根据用户名删除用户成功", user);
}

全局异常处理类中新增以下方法:


@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResult<Map<String, String>> handleConstraintViolationException(ConstraintViolationException e) {
   Map<String, String> errors = new HashMap<>(16);
   e.getConstraintViolations().forEach(constraintViolation -> errors
       .put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage()));
   return CommonResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), "无效的参数", errors);
}

发送如下GET请求,请求地址为http://localhost:8080/user/get/0

SpringBoot2.x 参数校验问题小结

发送如下DELETE请求,请求地址为http://localhost:8080/user/delete?username=ZhaoZiLong_1896582826

SpringBoot2.x 参数校验问题小结

五、校验Service中的参数

通过@Validated@Valid注解组合使用不仅可以校验Controller中的参数,还可以校验任何Spring组件中的参数。

UserService


package com.rtxtitanv.service;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;
import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.service.UserService
* @description UserService
* @date 2021/8/15 16:46
*/
@Validated
public interface UserService {
   /**
    * 更新用户
    *
    * @param user 用户参数
    * @return CommonResult<User>
    */
   CommonResult<User> updateUser(@Valid User user);
}

UserService实现类:


package com.rtxtitanv.service.impl;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.User;
import com.rtxtitanv.service.UserService;
import org.springframework.stereotype.Service;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.service.impl.UserServiceImpl
* @description UserService实现类
* @date 2021/8/15 16:46
*/
@Service
public class UserServiceImpl implements UserService {
   @Override
   public CommonResult<User> updateUser(User user) {
       return CommonResult.ok("更新用户成功", user);
   }
}

UserController中新增以下代码:


@Resource
private UserService userService;

@PutMapping("/update")
public CommonResult<User> updateUser(@RequestBody User user) {
   return userService.updateUser(user);
}

发送如下PUT请求,请求地址为http://localhost:8080/user/update

SpringBoot2.x 参数校验问题小结

六、编程式校验

通过Validator实例可以手动进行参数校验。UserController中新增以下代码:


@Resource
private Validator validator;

@PostMapping("/insert")
public CommonResult<User> insertUser(@RequestBody User user) {
   if (!validator.validate(user).isEmpty()) {
       throw new ConstraintViolationException(validator.validate(user));
   }
   return CommonResult.ok("添加用户成功", user);
}

发送如下POST请求,请求地址为http://localhost:8080/user/insert

SpringBoot2.x 参数校验问题小结

七、自定义校验注解

如果自带的校验注解无法满足需求,还可以自定义校验注解。首先创建如 * 解用于密码校验:


package com.rtxtitanv.annotation;

import com.rtxtitanv.validator.PasswordValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.annotation.Password
* @description 自定义密码校验注解
* @date 2021/8/16 14:54
*/
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {

String message() default "无效密码";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

然后创建ConstraintValidator接口的实现类并重写isValid方法:


package com.rtxtitanv.validator;

import com.rtxtitanv.annotation.Password;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.validator.PasswordValidator
* @description PasswordValidator
* @date 2021/8/16 15:01
*/
public class PasswordValidator implements ConstraintValidator<Password, String> {
   @Override
   public boolean isValid(String value, ConstraintValidatorContext context) {
       if (value == null) {
           return true;
       }
       // 密码必须以大写英文字母开头,只包含英文字母、数字、下划线,长度在6到20之间
       return value.matches("^[A-Z][A-Za-z0-9_]{5,19}$");
   }
}

创建如 * 解用于密码校验:


package com.rtxtitanv.annotation;

import com.rtxtitanv.validator.RankValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.annotation.Rank
* @description 自定义用户段位校验注解
* @date 2021/8/16 15:12
*/
@Documented
@Constraint(validatedBy = RankValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Rank {

String message() default "rank值无效";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

创建ConstraintValidator接口的实现类并重写isValid方法,:


package com.rtxtitanv.validator;

import com.rtxtitanv.annotation.Rank;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.validator.RankValidator
* @description RankValidator
* @date 2021/8/16 15:18
*/
public class RankValidator implements ConstraintValidator<Rank, String> {
   @Override
   public boolean isValid(String value, ConstraintValidatorContext context) {
       HashSet<String> ranks = new HashSet<>();
       ranks.add("无段位");
       ranks.add("青铜");
       ranks.add("白银");
       ranks.add("黄金");
       ranks.add("铂金");
       ranks.add("钻石");
       // 段位必须为无段位、青铜、白银、黄金、铂金、钻石之一
       return ranks.contains(value);
   }
}

修改User类,使用自定义校验注解:


package com.rtxtitanv.model;

import com.rtxtitanv.annotation.Password;
import com.rtxtitanv.annotation.Rank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.User
* @description 用户实体类
* @date 2021/8/15 16:28
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
   @NotNull(message = "id不能为空")
   private Long id;
   @Length(min = 6, max = 20, message = "用户名长度不小于6,不超过20")
   @NotNull(message = "用户名不能为空")
   private String username;
   @Password(message = "密码必须以大写英文字母开头,只包含英文字母、数字、下划线,长度在6到20之间")
   @NotNull(message = "密码不能为空")
   private String password;
   @Max(value = 60, message = "年龄最大为60")
   @Min(value = 18, message = "年龄最小为18")
   @NotNull(message = "年龄不能为空")
   private Integer age;
   @Email(message = "邮箱格式不正确")
   @NotEmpty(message = "邮箱不能为空")
   private String email;
   @Rank(message = "段位必须为无段位、青铜、白银、黄金、铂金、钻石之一")
   private String rank;
}

发送如下POST请求,请求地址为http://localhost:8080/user/save

SpringBoot2.x 参数校验问题小结

八、分组校验

在参数校验时如果想针对不同的方法使用不同的校验规则,则可以使用分组校验。下面创建两个用于分组校验的接口:


public interface AddUserGroup {}
public interface ModifyUserGroup {}

在User类的实例域id上添加以 * 解:


@NotNull(message = "id不能为空", groups = ModifyUserGroup.class)
@Null(message = "id必须为空", groups = AddUserGroup.class)

不写groups属性,则为默认分组。

UserController中新增以下方法:


@PostMapping("/add")
public CommonResult<User> addUser(@RequestBody @Validated(value = AddUserGroup.class) User user) {
   return CommonResult.ok("新增用户成功", user);
}

@PutMapping("/modify")
public CommonResult<User> modifyUser(@RequestBody @Validated(value = ModifyUserGroup.class) User user) {
   return CommonResult.ok("修改用户成功", user);
}

发送如下POST请求,请求地址为http://localhost:8080/user/add

SpringBoot2.x 参数校验问题小结

发送如下PUT请求,请求地址为http://localhost:8080/user/modify

SpringBoot2.x 参数校验问题小结

根据测试结果可知,方法addUser的参数使用的@Null校验,方法modifyUser的参数使用的@NotNull校验,成功实现了分组校验。

九、嵌套的参数校验

一个实体中嵌套一个实体时,通过在嵌套的实体类型属性上添加@Valid注解,可以对嵌套的参数进行校验。

Account类:


package com.rtxtitanv.model;

import com.rtxtitanv.annotation.Password;
import lombok.Data;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.Account
* @description 账户实体类
* @date 2021/8/17 16:25
*/
@Data
public class Account {
   @NotNull(message = "账户id不能为空")
   private Long accountId;
   @Size(min = 6, max = 20, message = "账户名长度不小于6,不超过20")
   @NotNull(message = "账户名不能为空")
   private String accountName;
   @Password(message = "密码必须以大写英文字母开头,只包含英文字母、数字、下划线,长度在6到20之间")
   @NotNull(message = "账户密码不能为空")
   private String accountPassword;
}

Order类:


package com.rtxtitanv.model;

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.model.Order
* @description 订单实体类
* @date 2021/8/17 16:55
*/
@Data
public class Order {
   @NotNull(message = "订单id不能为空")
   private Long orderId;
   @NotEmpty(message = "订单号不能为空")
   private String orderNumber;
   @NotEmpty(message = "订单描述信息不能为空")
   private String orderDescription;
   @Valid
   private Account account;
}

OrderController


package com.rtxtitanv.controller;

import com.rtxtitanv.model.CommonResult;
import com.rtxtitanv.model.Order;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
* @author rtxtitanv
* @version 1.0.0
* @name com.rtxtitanv.controller.OrderController
* @description OrderController
* @date 2021/8/17 17:00
*/
@RequestMapping("/order")
@RestController
@Validated
public class OrderController {

@PostMapping("/save")
   public CommonResult<Order> saveOrder(@RequestBody @Valid Order order) {
       return CommonResult.ok("订单保存成功", order);
   }
}

发送如下POST请求,请求地址为http://localhost:8080/order/save,发现嵌套的参数也被校验:

SpringBoot2.x 参数校验问题小结

代码示例

Github:https://github.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-validatorGitee:https://gitee.com/RtxTitanV/springboot-learning/tree/master/springboot2.x-learning/springboot-validator

来源:https://blog.csdn.net/RtxTitanV/article/details/119763108

标签:SpringBoot,参数,校验
0
投稿

猜你喜欢

  • 将文件夹下所有文件输出到日志文件中 c#递归算法学习示例

    2023-04-08 20:36:22
  • Springboot配置文件内容加密代码实例

    2022-09-13 05:56:09
  • MybatisPlus中@TableField注解的使用详解

    2021-11-01 23:05:35
  • 使用Java8实现模板方法模式的改造

    2021-10-28 23:46:09
  • 关于工厂方法模式的Java实现

    2021-12-20 18:06:13
  • java servlet手机app访问接口(三)高德地图云存储及检索

    2022-08-14 10:15:49
  • Java多线程wait()和notify()方法详细图解

    2021-09-19 20:27:32
  • 解决C#全屏幕截图的实现方法

    2023-10-25 00:41:15
  • java根据List内对象的属性排序方法

    2022-10-25 10:05:44
  • java面试题——详解HashMap和Hashtable 的区别

    2023-08-06 16:38:25
  • C#图片查看器实现方法

    2021-06-17 16:50:03
  • Android九宫格程序设计代码

    2022-05-04 13:11:49
  • Spring的异常重试框架Spring Retry简单配置操作

    2023-11-25 18:27:35
  • java request.getHeader("user-agent")获取浏览器信息的方法

    2022-05-28 01:04:26
  • 区块链java代码实现

    2023-02-04 04:00:24
  • Android编程之利用服务实现电话监听的方法

    2022-02-16 03:59:42
  • Android入门之TabHost与TabWidget实例解析

    2022-12-07 23:03:42
  • spring配置不扫描service层的原因解答

    2022-03-24 16:35:02
  • C#简单数字图像处理程序

    2022-03-07 05:16:31
  • C#中的应用程序接口介绍及实现,密封类与密封方法

    2023-10-24 07:37:41
  • asp之家 软件编程 m.aspxhome.com