首页 > 编程语言 >Java Spring Boot 参数校验及自定义校验

Java Spring Boot 参数校验及自定义校验

时间:2023-12-06 16:23:09浏览次数:51  
标签:Java String 自定义 校验 import ElementType public

在项目开发中,时常会碰到前端传递过来的请求参数需要校验,毕竟永远不要相信没有经过自己校验的数据,如果是零星几个参数,直接 if...else if ...else... 即可,但数据量大了,同时为了尽可能地增加复用,这里就可以用到参数校验了,如果你觉得框架提供的校验方法不够用,或者你的校验比较个性化,那就自定义校验

环境:

  • Spring Boot:3.1.6
  • JDK:17

声明:

接下来的内容主要基于 [1] 做一定改动,如果想看原文,请点击原文,链接在下面参考中。

1.主要注解

先看常用注解有哪些:

image.png

可以看到注解主要集中于以下几个方面:

  • 针对时间的,以前,现在,未来
  • 针对数值型的,最小,最大,正负情况
  • 针对字符串的,长度大小,是否空
  • 针对布尔值的,true or false

以下是主要注解:

  • @NotNull:值不能为null;

  • @NotEmpty:字符串、集合或数组的值不能为空,即长度大于0;

  • @NotBlank:字符串的值不能为空白,即不能只包含空格;

  • @Size:字符串、集合或数组的大小是否在指定范围内;

  • @Min:数值的最小值;

  • @Max:数值的最大值;

  • @DecimalMin:数值的最小值,可以包含小数;

  • @DecimalMax:数值的最大值,可以包含小数;

  • @Digits:数值是否符合指定的整数和小数位数;

  • @Pattern:字符串是否匹配指定的正则表达式;

  • @Email:字符串是否为有效的电子邮件地址;

  • @AssertTrue:布尔值是否为true;

  • @AssertFalse:布尔值是否为false;

  • @Future:日期是否为将来的日期;

  • @Past:日期是否为过去的日期;

2.注解使用

创建项目&&添加依赖

首先肯定还是先创建一个 Spring Boot web 项目,因为我们会用到参数校验,这里需要在 pom.xml 添加三方包依赖:

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

我们这里假设一种用户场景,后端根据前端提交过来的用户参数做校验,校验通过后,存入数据库中(项目演示为主,忽略数据库的使用),如果校验失败,将失败信息返回给前端。

校验的用户类

package com.example.springbootparamvalidatedemo.param;

import com.example.springbootparamvalidatedemo.util.Phone;
import jakarta.validation.constraints.*;
import lombok.Data;

@Data
public class User {
    @NotBlank(message = "用户名不能为空")
    private String name;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 30, message = "密码长度在6到30之间")
    private String password;

    @Min(value = 18, message = "必须成年")
    @Max(value = 120, message = "不得超过年龄极限")
    private int age;

    @Pattern(regexp = "^(18[0-9])\d{8}$", message = "格式不正确")
    private String phone;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

在用户类中,我们仅通过几个基本字段,根据字段属性,添加一定的校验注解。

统一的响应体

通常在项目开发中,我们都会用到统一的响应数据格式,所以这里将响应格式封装后,作为工具类供调用。

package com.example.springbootparamvalidatedemo.util;

import lombok.Data;

import java.io.Serializable;

@Data
public class Resp<T> implements Serializable {
    private int code;
    private boolean success;
    private T data;
    private String msg;

    private Resp(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
        this.success = code == 200;
    }

    public static <T> Resp<T> ok(T data) {
        return new Resp<>(200, data, null);
    }

    public static <T> Resp<T> error(String msg) {
        return new Resp<>(500, null, msg);
    }
}

上面的响应体中,主要简单分为 成功和失败 的两种情况,考虑到通用,我们引入 泛型。

controller类中应用

这里就是简单根据前端传入的用户参数做校验,然后返回响应。

package com.example.springbootparamvalidatedemo.controller;

import com.example.springbootparamvalidatedemo.param.User;
import com.example.springbootparamvalidatedemo.util.Resp;

import jakarta.validation.Valid;
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;

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/save")
    public Resp save(@Valid @RequestBody User user) { // @Valid 表示校验参数
        return Resp.ok(user);
    }
}

为了让项目更加完善,引入全局异常处理,对最后的响应做拦截输出。

全局异常处理

package com.example.springbootparamvalidatedemo.exception;

import com.example.springbootparamvalidatedemo.util.Resp;

import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public Resp handleError(BindException e) {
        BindingResult bindingResult = e.getBindingResult();

        return Resp.error(bindingResult.getFieldError().getDefaultMessage());
    }
}

校验演示

我们启动项目,然后在 postman 中测试看看。

这里假定传入了 age 不合规定的值:
image.png

传入全部合规的值:

image.png

从上面结果来看,参数校验是可以用的。

3.自定义注解

自定义实现

尽管框架提供一些校验规则,难免遇到一些现有规则不能覆盖的情况,这里我们就一些特定情况做个自定义的参数校验。

我们可以先观察下现有校验规则是怎样的:

Min

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Min.List.class)
@Documented
@Constraint(
    validatedBy = {}
)
public @interface Min {
    String message() default "{jakarta.validation.constraints.Min.message}";

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

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

    long value();

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        Min[] value();
    }
}

NotBlank

@Documented
@Constraint(
    validatedBy = {}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotBlank.List.class)
public @interface NotBlank {
    String message() default "{jakarta.validation.constraints.NotBlank.message}";

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

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

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        NotBlank[] value();
    }
}

理论上说,我们写个类似的注解接口,是不是就可以用呢?

实际上,在做自定义的参数校验的时候,除了自定义的注解接口,另外我们还需要再实现一个接口 ConstraintValidator

我们在 User 类中加个参数,比如加个用户地区,限定 Asia 或 Ameraica,只有这两个地区的人才可以注册。

下面开始我们的自定义编码。

Address

package com.example.springbootparamvalidatedemo.util;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AddressValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD,
        ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
        ElementType.PARAMETER, ElementType.TYPE_USE})
public @interface Address {
    String message() default "不在合法地区范围内";

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

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

实现类

package com.example.springbootparamvalidatedemo.util;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Arrays;

public class AddressValidator implements ConstraintValidator<Address, String> {
    private static  String[] addresses = {"Asia", "America"};

    @Override
    public boolean isValid(String address, ConstraintValidatorContext context) {
        if (Arrays.asList(addresses).contains(address)) {
            return true;
        }

        return false;
    }
}

在 User类 中加入 address 字段:

public String getAddress() {
    return address;
}

public void setAddress(String address) {
    this.address = address;
}

@NotBlank(message = "地址非空")
@Address
private String address;

测试:
address 不在范围内的

image.png

address 在范围内

image.png

可以看到这里是生效了的。

自定义要点

注解接口:

  • @Constraint(validatedBy = {PhoneValidator.class}):用于指定验证器类;

  • @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}):指定@Phone注解可以作用在方法、字段、构造函数、参数以及类型上

实现类[2]:

想让自定义验证注解生效,需要实现 ConstraintValidator 接口。接口的第一个参数是 自定义注解类型,第二个参数是 被注解字段的类型。这里因为订单ID 是 String 类型,我们第二个参数定义为 String 就可以了,需要提到的一点是 ConstraintValidator 接口的实现类无需添加 @Component 它在启动的时候就已经被加载到容器中了。

参考:

标签:Java,String,自定义,校验,import,ElementType,public
From: https://www.cnblogs.com/davis12/p/17879793.html

相关文章

  • java与算法Day1 Scanner的应用(一)
    java中使用输入需要用到java.util.Scanner。Scanner有next,nextInt,nextString,hasNext,hasNextLine等方法。使用XXX variable=Scanner.NextXXX就可以获取一个输入值。next系列的方法,他们的作用都是从键盘中接收数据。当程序执行到他们的时候,在命令行中就开始等待键盘输入了,而......
  • 【JavaScript高级程序设计】-3语言基础
    3.1语法.........................................................213.1.1区分大小写..................................213.1.2标识符..........................................213.1.3注释..............................................223.1.4严格模式......
  • SpringBoot项目中集成自定义公共Maven依赖如何集成与调试
    场景Nexus-在项目中使用Maven私服,Deploy到私服、上传第三方jar包、在项目中使用私服jar包:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/101391279Maven项目在pom文件中引入lib下的第三方jar包并打包进去:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/det......
  • kettle从入门到精通 第二十三课 kettle carte 错误(java.lang.OutOfMemoryError: GC ov
     1、Couldnotemitbufferduetolackofrequests(无法发出缓冲区,因为请求不足。)原因有两点:1)消费者处理数据能力较弱,如表输出步骤。2)消费者没有处理数据,如传递的数据中有字段type=1的数据,这种情况没有处理。解放方案:针对1)采用多线程处理和开启批量提交,如下图所示批量插入......
  • 【Java 进阶篇】Java会话技术之Cookie的存活时间
    在Web应用程序中,会话管理是一项关键的任务,用于跟踪用户的活动和保持用户状态。Cookie是会话管理的一种重要方式之一,通过Cookie可以存储有关用户的信息,但这些信息不会永久保留,而是有一个特定的存活时间。本篇博客将详细介绍Java中Cookie的存活时间,包括如何设置、修改和管理Cookie的......
  • 【Java 进阶篇】Java Session 原理及快速入门
    大家好,欢迎来到本篇博客。今天,我们将探讨JavaWeb开发中一个重要而令人兴奋的概念,即Session(会话)。Session是一种在Web应用程序中跟踪用户状态和数据的机制。我们将深入了解Session的原理,并通过示例来快速入门。什么是Session?在Web开发中,Session是一种服务器端的机制,用于跟踪用户与W......
  • java字符串String类的常用方法
    java字符串String类的常用方法字符串的创建:(1)定义字符串直接赋值,在字符串池中开辟空间()Stringstr1=“Hello”;//在字符串池中写入字符串"hello"Stringstr2=“Hello”;//直接引用字符串池中的"Hello"System.out.println(str1==str2);//地址相同,输出:true(2)使用new关键字......
  • JS(JavaScript)-if-switch选择结构-for-while循环
     前言:回到JS基础,用console输出; console.log(); 输入内容:window.prompt();向页面做出输入接收(类似于后端Scanner) 整数转换:parseInt();1.选择结构:①if结构if(){};  if(){}else{};  if(){}elseif{};......
  • Java并发编程进阶
    并发编程是现代软件开发中的一个关键技能。在Java中,java.util.concurrent包提供了一系列构建块,可以帮助开发者编写并发代码。这篇文章将深入探讨线程池、并发集合、同步器,以及Java内存模型。线程池线程池是一种资源池,它管理着一组可复用的线程。使用线程池可以减少在创建和销毁线程......
  • JavaScript
    JavaScript的特点:js是脚本语言js是解释性语言,根据代码顺序逐一解释,有一行报错,js就会卡在此处无法进入到下一步js是一种安全性语言,具有web安全特性不允许访问本地硬盘,也不允许对网络文件进行修改,只能通过浏览器进行浏览或者动态交互js有跨平台性js输出的关键字有三个1.alert......