SpringBoot2.x 参数校验问题小结

网友投稿 858 2022-12-17

SpringBoot2.x 参数校验问题小结

SpringBoot2.x 参数校验问题小结

目录一、引入依赖二、实体类三、常用的校验注解四、校验Controller中的参数五、校验Service中的参数六、编程式校验七、自定义校验注解八、分组校验九、嵌套的参数校验

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

一、引入依赖

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-validation

org.projectlombok

lombok

1.18.8

二、实体类

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 implements Serializable {

private static final long serialVersionUID = 5231430760082814286L;

private int code;

private String message;

private T data;

public static CommonResult ok(String message, T data) {

return new CommonResult(HttpStatus.OK.value(), message, data);

}

public static CommonResult 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 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> validateException(MethodArgumentNotValidException e) {

Map 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

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 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> handleConstraintViolationException(ConstraintViolationException e) {

Map 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

*/

CommonResult 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 updateUser(User user) {

return CommonResult.ok("更新用户成功", user);

}

}

UserController中新增以下代码:

@Resource

private UserService userService;

@PutMapping("/update")

public CommonResult updateUser(@RequestBody User user) {

return userService.updateUser(user);

}

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

六、编程式校验

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

@Resource

private Validator validator;

@PostMapping("/insert")

public CommonResult 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 {

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

@Override

public boolean isValid(String value, ConstraintValidatorContext context) {

HashSet 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 addUser(@RequestBody @Validated(value = AddUserGroup.class) User user) {

return CommonResult.ok("新增用户成功", user);

}

@PutMapping("/modify")

public CommonResult 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 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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用springboot打包后的文件读取方式
下一篇:SpringSecurity数据库进行认证和授权的使用
相关文章

 发表评论

暂时没有评论,来抢沙发吧~