超详细讲解SpringBoot参数校验实例

目录
  • 使用传统方式的弊端
  • 引入依赖
  • 注解说明
  • 一、对实体类进行校验
    • 1、entity
    • 2、controller
    • 3、编写全局统一异常处理
  • 二、针对单个参数进行校验
  • 三、分组校验
    • 1、entity
    • 2、controller
  • 四、自定义分组校验
    • 1、entity
    • 2、CustomSequenceProvider
    • 3、controller
  • 五、自定义校验
    • 1、定义校验注解
    • 2、实现注解
  • 六、嵌套校验
  • 七、快速失败
  • 注意事项
  • 总结

使用传统方式的弊端

public String addUser(User user) {
     if (user == null || user.getId() == null || user.getAccount() == null || user.getPassword() == null || user.getEmail() == null) {
         return "对象或者对象字段不能为空";
     }
     if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) {
         return "不能输入空字符串";
     }
     if (user.getAccount().length() < 6 || user.getAccount().length() > 11) {
         return "账号长度必须是6-11个字符";
     }
     if (user.getPassword().length() < 6 || user.getPassword().length() > 16) {
         return "密码长度必须是6-16个字符";
     }
     if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) {
         return "邮箱格式不正确";
     }
     // 参数校验完毕后这里就写上业务逻辑
     return "success";
 }

这样做确实没有什么问题,而且排版也工整,但代码太繁琐了,如果有几十个字段要校验,那这个方法里面将会变得非常臃肿,实在不够优雅。下面我们就来讲讲如何使用最优雅的方式来解决。

引入依赖

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

注解说明

注解 说明
@AssertFalse 被注解的元素必须为 false
@AssertTrue 被注解的元素必须为 true
@DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@Digits (integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内
@Null 被注解的元素必须为空
@NotNull 被注解的元素必须不为空
@Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最大值
@Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min) 被注解的元素的长度必须在指定的范围内
@Past 被注解的元素必须是一个过去的日期
@Future 被注解的元素必须是一个未来的日期
@Pattern(value) 被注解的元素必须符合指定的正则表达式

下面我们以此来在业务中实现

一、对实体类进行校验

1、entity

@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Long id;
    @NotNull(message = "用户账号不能为空")
    @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
    private String account;
    @NotNull(message = "用户密码不能为空")
    @Size(min = 6, max = 11, message = "密码长度必须是6-16个字符")
    private String password;
    @NotNull(message = "用户邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

2、controller

@RestController
public class UserController {
    @PostMapping("/addUser")
    public void addUser(@RequestBody @Valid User user) {
		//业务
    }
}

3、编写全局统一异常处理

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;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
/**
 * 全局异常处理
 *
 * @author master
 */
@RestControllerAdvice
public class ExceptionConfig {
    /**
     * 参数为实体类
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public String handleValidException(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回
        return objectError.getDefaultMessage();
    }
    /**
     * 参数为单个参数或多个参数
     * @param e
     * @return
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public String handleConstraintViolationException(ConstraintViolationException e) {
        // 从异常对象中拿到ObjectError对象
        return e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
            	.collect(Collectors.toList()).get(0);
    }
}

然后我们使用apipost测试

二、针对单个参数进行校验

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;
@RestController
@Validated
public class TestController {
    @GetMapping("/test")
    public void test(@NotNull(message = "id不能为空") Integer id) {
    }
}

然后我们使用apipost测试

三、分组校验

场景:在新增时我们需要id为空,但修改时我们又需要id不为空,总不可能搞两个类吧,这时候分组校验的用处就来了

1、entity

import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
public class User {
    public interface Insert{
    }
    public interface Update{
    }
    @NotNull(message = "用户id不能为空",groups = Update.class)
    @Null(message = "用户id必须为空",groups = Integer.class)
    private Long id;
    private String account;
    private String password;
    private String email;
}

2、controller

@PostMapping("/add")
public void add(@RequestBody @Validated(User.Insert.class) User user) {
}

添加时就用User.Insert.class,修改时就用User.Update.class

四、自定义分组校验

场景:当type为1时,需要参数a不为空,当type为2时,需要参数b不为空。

1、entity

import com.example.demo.provider.CustomSequenceProvider;
import lombok.Data;
import org.hibernate.validator.group.GroupSequenceProvider;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@Data
@GroupSequenceProvider(value = CustomSequenceProvider.class)
public class CustomGroup {
    /**
     * 类型
     */
    @Pattern(regexp = "[A|B]" , message = "类型不必须为 A|B")
    private String type;
    /**
     * 参数A
     */
    @NotEmpty(message = "参数A不能为空" , groups = {WhenTypeIsA.class})
    private String paramA;
    /**
     * 参数B
     */
    @NotEmpty(message = "参数B不能为空", groups = {WhenTypeIsB.class})
    private String paramB;
    /**
     * 分组A
     */
    public interface WhenTypeIsA {
    }
    /**
     * 分组B
     */
    public interface WhenTypeIsB {
    }
}

2、CustomSequenceProvider

import com.example.demo.controller.CustomGroup;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
import java.util.ArrayList;
import java.util.List;
public class CustomSequenceProvider implements DefaultGroupSequenceProvider<CustomGroup> {
    @Override
    public List<Class<?>> getValidationGroups(CustomGroup form) {
        List<Class<?>> defaultGroupSequence = new ArrayList<>();
        defaultGroupSequence.add(CustomGroup.class);
        if (form != null && "A".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroup.WhenTypeIsA.class);
        }
        if (form != null && "B".equals(form.getType())) {
            defaultGroupSequence.add(CustomGroup.WhenTypeIsB.class);
        }
        return defaultGroupSequence;
    }
}

3、controller

@PostMapping("/add")
public void add(@RequestBody @Validated CustomGroup user) {
}

五、自定义校验

虽然官方提供的校验注解已经满足很多情况了,但还是无法满足我们业务的所有需求,比如校验手机号码,下面我就以校验手机号码来做一个示例。

1、定义校验注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号码格式有误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

注:groups和payload是必须要写的,Constraint是使用哪个类来进行校验。

2、实现注解

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
/**
 * @author master
 */
public class PhoneValidator implements ConstraintValidator<Phone, Object> {
    @Override
    public boolean isValid(Object telephone, ConstraintValidatorContext constraintValidatorContext) {
        String pattern = "^1[3|4|5|6|7|8|9]\\d{9}$";
        return Pattern.matches(pattern, telephone.toString());
    }
}

最后直接用到参数前面或者实体类变量上面即可。

六、嵌套校验

当某个对象中还包含了对象需要进行校验,这个时候我们需要用嵌套校验。

@Data
public class TestAA {
    @NotEmpty(message = "id不能为空")
    private String id;
    @NotNull
    @Valid
    private Job job;
    @Data
    public class Job {
        @NotEmpty(message = "content不能为空")
        private String content;
    }
}

七、快速失败

Spring Validation默认会校验完所有字段,然后才抛出异常。可以通过配置,开启Fali Fast模式,一旦校验失败就立即返回。

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 FailFastConfig {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

注意事项

SpringBoot 2.3.x 移除了validation依赖需要手动引入依赖。

总结

非空校验是校验的第一步, 除了非空校验,我们还需要做到以下几点:

  • 普通参数 - 需要限定字段的长度。如果会将数据存入数据库,长度以数据库为准,反之根据业务确定。
  • 类型参数 - 最好使用正则对可能出现的类型做到严格校验。比如type的值是【0|1|2】这样的。
  • 列表(list)参数 - 不仅需要对list内的参数是否合格进行校验,还需要对list的size进行限制。比如说 100。
  • 日期,邮件,金额,URL这类参数都需要使用对于的正则进行校验。
  • 参数真实性 - 这个主要针对于 各种Id 比如说 userId、merchantId,对于这样的参数,都需要进行真实性校验

参数校验越严格越好,严格的校验规则不仅能减少接口出错的概率,同时还能避免出现脏数据,从而来保证系统的安全性和稳定性。

到此这篇关于超详细讲解SpringBoot参数校验实例的文章就介绍到这了,更多相关SpringBoot参数校验内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解SpringBoot中的参数校验(项目实战)

    Java后端发工作中经常会对前端传递过来的参数做一些校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,充满了if-else这种校验代码,在代码中相当冗长.例如说,用户注册时,会校验手机格式的正确性,用户名的长度等等.虽说前端也可以做参数校验,但是为了保证我们API接口的可靠性,以保证最终数据入库的正确性,后端进行参数校验不可忽视. Hibernate Validator 提供了一种统一方便的方式,让我们快速的实现参数校验. Hibernate Validator 使用注解,实现声明式校验

  • SpringBoot参数校验的方法总结

    一.前言 在上一篇MyBatis-plus 初体验 中已经简单实现了 MyBatis-Plus 数据库查询.我们知道 CURD 离不开前后端的数据交互,因此参数校验是必不可少的.这篇主要讲一下 SpringBoot 参数校验. 在 Web 开发中经常需要对前端传过来的参数进行校验,例如格式校验.非空校验等,基本上每个接口都需要进行校验.如果使用常规的 IF ELSE 进行校验,随着参数越来越多,校验逻辑的冗余度也越来越高,导致维护性变差.在 Java 中定义了一套基于注解的数据校验规范 Bean

  • SpringBoot 如何自定义请求参数校验

    目录 一.Bean Validation基本概念 二.基本用法 三.自定义校验 3.1 自定义注解 3.2 自定义Validator 3.3 以编程的方式校验(手动) 3.4 定义分组校验 3.5 定制返回码和消息 3.6 更加细致的返回码和消息 四.小结 最近在工作中遇到写一些API,这些API的请求参数非常多,嵌套也非常复杂,如果参数的校验代码全部都手动去实现,写起来真的非常痛苦. 正好Spring轮子里面有一个Validation,这里记录一下怎么使用,以及怎么自定义它的返回结果. 一.B

  • SpringBoot实现接口的各种参数校验的示例

    目录 1.添加依赖 2.接口参数校验 2.1 requestBody参数校验 2.2 requestParam/PathVariable参数校验 3.统一异常处理 4.进阶使用 4.1 分组校验 4.2 嵌套校验 4.3 集合校验 4.4 自定义校验 5.快速失败 (Fail Fast) 6.@Valid和@Validated区别 7.实现原理 7.1 requestBody参数校验实现原理 7.2 方法级别的参数校验实现原理 在我们进行接口开发时,在对参数的接收时,我们需要冗余复杂的校验规则

  • SpringBoot各种参数校验的实例教程

    目录 简单使用 引入依赖 requestBody参数校验 requestParam/PathVariable参数校验 统一异常处理 进阶使用 分组校验 嵌套校验 集合校验 自定义校验 编程式校验 快速失败(Fail Fast) @Valid和@Validated区别 实现原理 requestBody参数校验实现原理 方法级别的参数校验实现原理 总结 简单使用 Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现.hibernate valida

  • 超详细讲解SpringBoot参数校验实例

    目录 使用传统方式的弊端 引入依赖 注解说明 一.对实体类进行校验 1.entity 2.controller 3.编写全局统一异常处理 二.针对单个参数进行校验 三.分组校验 1.entity 2.controller 四.自定义分组校验 1.entity 2.CustomSequenceProvider 3.controller 五.自定义校验 1.定义校验注解 2.实现注解 六.嵌套校验 七.快速失败 注意事项 总结 使用传统方式的弊端 public String addUser(User

  • C++超详细讲解函数参数的默认值

    目录 1.参数默认值的指定方式 2.参数默认值的指定规则 1.参数默认值的指定方式 在 C++ 中,可以在函数声明时为参数提供一个默认值.这样在函数调用时,如果没有提供函数参数值,则使用默认值. e.g. 在函数声明时,指定参数默认值 void Demo(int x = 0); int main(int argc, char* argv[]) { Demo(); // 不用提供参数,会将 x = 0 的结果输出 return 0; } void Demo(int x) { printf("x =

  • Redis缓存实例超详细讲解

    目录 1 前言 1.1 什么是缓存 1.2 缓存的作用及成本 1.3 Redis缓存模型 2 给商户信息添加缓存 3 缓存更新策略 3.1 更新策略介绍 3.2 主动更新策略 3.3 主动更新策略练习 4 缓存穿透及其解决方案 4.1 缓存穿透的概念 4.2 解决方案及实现 5 缓存雪崩的概念及其解决方案 6 缓存击穿及解决方案 6.1 什么是缓存击穿 6.2 缓存击穿解决方法 6.2.1 互斥锁 6.2.2 逻辑过期 1 前言 1.1 什么是缓存 缓存就是数据交换的缓冲区(称作Cache [

  • SpringBoot封装响应处理超详细讲解

    目录 背景 报文基本格式 创建枚举类 定义统一返回结果实体类 定义返回工具类 统一报文封装在接口中的使用 统一异常处理 小结 背景 越来越多的项目开始基于前后端分离的模式进行开发,这对后端接口的报文格式便有了一定的要求.通常,我们会采用JSON格式作为前后端交换数据格式,从而减少沟通成本等. 报文基本格式 一般报文格式通常会包含状态码.状态描述(或错误提示信息).业务数据等信息. 在此基础上,不同的架构师.项目搭建者可能会有所调整. 但从整体上来说,基本上都是大同小异. 在SpringBoot项

  • SpringBoot嵌入式Servlet容器与定制化组件超详细讲解

    目录 嵌入式Servlet容器 1.原理分析 2.Servlet容器切换 3.定制Servlet容器配置 定制化组件 嵌入式Servlet容器 在Spring Boot中,默认支持的web容器有 Tomcat, Jetty, 和 Undertow 1.原理分析 那么这些web容器是怎么注入的呢?我们一起来分析一下 当SpringBoot应用启动发现当前是Web应用,它会创建一个web版的ioc容器ServletWebServerApplicationContext 这个类下面有一个createW

  • C语言可变参数与内存管理超详细讲解

    目录 概述 动态分配内存 重新调整内存的大小和释放内存 概述 有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数.C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数.下面的实例演示了这种函数的定义. int func(int, ... ) { . . . } int main() { func(2, 2, 3); func(3, 2, 3, 4); } 请注意,函数func()最后一个参数写成省略号,即三个点号(...)

  • C语言超详细讲解栈与队列实现实例

    目录 1.思考-1 2.栈基本操作的实现 2.1 初始化栈 2.2 入栈 2.3 出栈 2.4 获取栈顶数据 2.5 获取栈中有效元素个数 2.6 判断栈是否为空 2.7 销毁栈 3.测试 3.1测试 3.2测试结果 4.思考-2 5.队列的基本操作实现 5.1 初始化队列 5.2 队尾入队列 5.3 队头出队列 5.4 队列中有效元素的个数 5.5 判断队列是否为空 5.6 获取队头数据 5.7 获取队尾的数据 5.8 销毁队列 6.测试 6.1测试 6.2 测试结果 1.思考-1 为什么栈用

  • 四个实例超详细讲解Java 贪心和枚举的特点与使用

    目录 贪心: 枚举: 1.朴素枚举 2.状压枚举    算法题1: 示例 算法题2: 示例 算法题3:  示例1 示例2 算法题4:  示例1 笔试技巧:学会根据数据范围猜知识点          一般1s 时间限制的题目,时间复杂度能跑到 1e8 左右( python 和 java 会少一些,所以建议大家使用c/c++ 做笔试题). n 范围 20 以内: O(n*2^n) 状压搜索 /dfs 暴搜 n 范围 200 以内: O(n^3) 三维 dp n 范围 3000 以内: O(n^2)

  • 四个实例超详细讲解Java 贪心和枚举的特点与使用

    目录 贪心: 枚举: 1.朴素枚举 2.状压枚举 算法题1: 示例 算法题2: 示例 算法题3: 示例1 示例2 算法题4: 示例 笔试技巧:学会根据数据范围猜知识点 一般1s 时间限制的题目,时间复杂度能跑到 1e8 左右( python 和 java 会少一些,所以建议大家使用c/c++ 做笔试题). n 范围 20 以内: O(n*2^n) 状压搜索 /dfs 暴搜 n 范围 200 以内: O(n^3) 三维 dp n 范围 3000 以内: O(n^2) 二维 dp 背包 枚举 二维前

  • SpringBoot超详细讲解集成Flink的部署与打包方法

    目录 一.SpringBoot集成Flink 二.FlinkTask写法调整 三.打包插件 四.Flink的上传与运行 总结 一.SpringBoot集成Flink 其实没什么特别的,就把Flink依赖的包在pom引入就行了.只是FlinkTask的写法要小调整下,把相关依赖交给spring管理就行. 然后如果放弃Flink的Dashboard端监控task执行相关信息,那也可以在SpringBoot的启动类里调用就行,但是可能出现task的相关对象没有注入,这种都是小问题(实际就是spring

随机推荐