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
:
如果验证失败会抛出MethodArgumentNotValidException
,默认情况下,Spring会将MethodArgumentNotValidException
异常转换为HTTP Status 400。查看控制台打印的日志发现抛出了MethodArgumentNotValidException
:
创建全局异常处理类捕获异常并进行处理:
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请求:
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
:
发送如下DELETE请求,请求地址为http://localhost:8080/user/delete?username=ZhaoZiLong_1896582826
:
五、校验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
:
六、编程式校验
通过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
:
七、自定义校验注解
如果自带的校验注解无法满足需求,还可以自定义校验注解。首先创建如 * 解用于密码校验:
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
:
八、分组校验
在参数校验时如果想针对不同的方法使用不同的校验规则,则可以使用分组校验。下面创建两个用于分组校验的接口:
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
:
发送如下PUT请求,请求地址为http://localhost:8080/user/modify
:
根据测试结果可知,方法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
,发现嵌套的参数也被校验:
代码示例
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
猜你喜欢
- 原因:feign传值出错无法接收到传值由于是POST所以添加@RequestBody进行尝试解决:错误原因是未添加@RequestBody尝
- 循环队列结构队列特点队列为一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,
- 资源服务器就是业务服务 如用户服务,订单服务等 第三方需要到资源服务器调用接口获取资源ResourceServerConfigResourc
- 这里我们以拨打电话申请权限来写个小例子,也就是CALL_PHONE,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限,在Andro
- URL(Uniform Resource Locator)是统一资源 * ,它是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,
- 前情提要我们上节内容学习了如何创建\注册\读取bean我们发现bean对象操作十分的繁琐!所以我们这个章节,就带大家来了解更加简单的bean
- 前言本文主要给大家介绍的是关于Java对xls文件进行读写操作的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:wi
- 操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observa
- 命令模式定义:将请求封装成对象,这可以让你使用不同的请求、队列、或者日志来参数化其他对象。何时使用命令模式?当需要将发出请求的对象和执行请求
- Java程序设计 图形用户界面 【十】复选框复选框JCheckBoxJCheckBox类方法作用public JCheckBox(Icon
- 简单工厂简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。定义了一个创建对象的类,由
- 最近在用ssm框架做一个管理系统,做到登录验证时,使用了下面的代码生成图片验证码,最终的效果如下图。Java类public class Ra
- 在Java编程中经常碰到类型转换,对象类型转换主要包括向上转型和向下转型。向上转型我们在现实中常常这样说:这个人会唱歌。在这里,我们并不关心
- 什么是JMMJMM全称Java Memory Model, 中文翻译Java内存模型,一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问
- 目的官方的Drools范例大都是基于纯Java项目或Maven项目,而基于Spring Boot项目的很少。本文介绍如何在Spring Bo
- Java中的引用类型有哪几种?Java中的引用类型分成 强引用 , 软引用 , 弱引用 , 虚引用 。1、强引用没有引用指向这个对象,垃圾回
- 前面我们完成了与商品类别相关的业务逻辑,接下来我们开始做具体商品部分。1. 数据库建表并映射Model首先我们在数据库中新建一张表,然后使用
- 用法一:常量在JDK1.5之前,我们定义常量都是:publicstaticfianl....。现在好了,有了枚举,可以把相关的常量分组到一个
- 0 写在前面在实际工作中有一些地方需要用到截取字符串的方法,所以在此记录下截取字符串的几种方法。.substring()StringUtil
- 参数传递即将参数传输到程序后台中,后台可能做一些处理,然后再将内容存入数据库之类嗒!参数传递的方法较多,一一说明如下。1、Action中直接