Java校验validate

Java校验validate

日常开发中,我们时常需要提供可靠的 API 接口,此时对于请求的入参就需要校验,以保证最终数据入库的正确性,这就成了必不可少的活。例如说,用户注册时,会校验手机格式的正确性、邮箱格式的正确性、密码非弱密码等。

但是如果使用 if-else 这种代码去校验, 那么需要校验的地方有很多情况下,代码量就会变的十分臃肿,若是一个类请求参数校验字段又多的化,相信各位小伙伴对不会开心,这么干肯定不合适,代码也不优雅,那么如何解决这个问题呢?

答案就是下面要介绍的 validation

validation介绍

validation 技术在Java中运用最早在2009 年,Java 官方提出了 Bean Validation 规范,而后经历了JSR303、JSR349、JSR380 三次标准的更迭,发展到了 2.0 。

Bean Validation 和 我们以前学习过的 JPA 一样,只提供规范,不提供具体的实现。因此实际使用过程,常用的是 hibernate 的校验组件:org.hibernate.hibernate-validator

常见的注解

通常情况下,在javax.validation.constraints 包下,定义了一系列的约束(constraint)注解,一共 22 个注解,快速略过即可。如下:

空和非空检查

  • @NotBlank:只能用于字符串不为 null ,并且字符串 .trim() 以后 length 要大于 0 。
  • @NotEmpty:集合对象的元素不为 0 ,即集合不为空 。
  • @NotNull:不能为 null 。
  • @Null:必须为 null 。

数值检查

  • @DecimalMax(value):被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
  • @DecimalMin(value):被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
  • @Digits(integer, fraction):被注释的元素必须是一个数字,其值必须在可接受的范围内。
  • @Positive:判断正数。
  • @PositiveOrZero:判断正数或 0 。
  • @Max(value):该字段的值只能小于或等于该值。
  • @Min(value):该字段的值只能大于或等于该值。
  • @Negative:判断负数。
  • @NegativeOrZero:判断负数或 0 。

Boolean 值检查

  • @AssertFalse:被注释的元素必须为 true 。
  • @AssertTrue:被注释的元素必须为 false 。

长度检查

  • @Size(max, min):检查字段的 size 是否在 min 和 max 之间,可以是字符串、数组、集合、Map 等。

日期检查

  • @Future:被注释的元素必须是一个将来的日期。
  • @FutureOrPresent:判断日期是否是将来或现在日期。
  • @Past:检查该字段的日期是在过去。
  • @PastOrPresent:判断日期是否是过去或现在日期。

其它检查

  • @Email:被注释的元素必须是电子邮箱地址。
  • @Pattern(value):被注释的元素必须符合指定的正则表达式。

Hibernate Validator 附加的约束注解,在org.hibernate.validator.constraints 包下,定义了一系列的约束(constraint)注解。常见的如示。

  • @Range(min=, max=):被注释的元素必须在合适的范围内。
  • @Length(min=, max=):被注释的字符串的大小必须在指定的范围内。
  • @URL(protocol=,host=,port=,regexp=,flags=):被注释的字符串必须是一个有效的 URL 。
  • @SafeHtml:判断提交的 HTML 是否安全。例如说,不能包含 javascript 脚本等等。

其他的就不一一列举了,有感兴趣的小伙伴可以去源码包看看。

@Valid和 @Validated

  • @Valid 注解,是 Bean Validation 所定义,可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。
  • @Validated 注解,是 Spring Validation 锁定义,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验。同时,@Validated 有 value 属性,支持分组校验。

对于初学者来说,很容易搞混 @Valid 和 @Validated 注解。

  • ① 声明式校验:Spring Validation 仅对 @Validated 注解,实现声明式校验。
  • ② 分组校验:Bean Validation 提供的 @Valid注解,因为没有分组校验的属性,所以无法提供分组校验。此时,我们只能使用 @Validated 注解。
  • ③ 嵌套校验:相比来说,@Valid注解的地方,多了【成员变量】。这就导致,如果有嵌套对象的时候,只能使用@Valid注解。

快速入门

在 pom.xml 文件中,引入相关依赖。

text 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>
`&lt;groupId&gt;com.raos&lt;/groupId&gt;
&lt;artifactId&gt;validation-demo&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;dependencies&gt;
    &lt;!-- 实现对 Spring MVC 的自动化配置 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- lombok依赖(代码简洁处理) --&gt;
	&lt;dependency&gt;
        &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
        &lt;artifactId&gt;lombok&lt;/artifactId&gt;
        &lt;scope&gt;compile&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;!-- 单元测试依赖 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
`

</project>

spring-boot-starter-web 依赖里,已经默认引入 hibernate-validator 依赖,所以本示例使用的是 Hibernate Validator 作为 Bean Validation 的实现框架。

1、简单校验

编写实体类(这里以常用的用户注册为场景)

text 复制代码
@Data

public class SysUser {

private Long userId;

/**

* 账号

/  
@NotBlank(message = "用户名不能为空")  
@Size(min = 6, message = "用户名长度不能小于6个字符")  
private String username;  
/*

* 密码

/  
@NotEmpty(message = "密码不能为空")  
@Size(min = 8, message = "密码长度不能小于8个字符")  
private String password;  
/*

* 手机号

*/

@NotBlank(message = "手机号不能为空")

@Size(min = 11, max = 11, message = "手机号长度不对")

private String mobile;

}

编写前端控制器

text 复制代码
@RestController

@RequestMapping("/user")

public class SysUserController {

@PostMapping("/add")

public R addUser(@RequestBody @Valid SysUser sysUser) {

System.out.println("走到这里说明校验成功");

System.out.println(sysUser);

return R.ok(R.SUCCESS_MSG);

}

}

编写前端响应封装实体

text 复制代码
public class R extends HashMap<String, Object> {

private static final long serialVersionUID = 1L;

public static final String SUCCESS_MSG = "操作成功!";

public static final String FAIL_MSG = "操作失败!";

public R() {

this.put((String) "code", 0);

}

public static R error() {

return error(500, "未知异常,请联系管理员");

}
`public static R error(String msg) {
    return error(500, msg);
}
public static R error(int code, String msg) {
    R r = new R();
    r.put((String) "code", code);
    r.put((String) "msg", msg);
    return r;
}
public static R ok(String msg) {
    R r = new R();
    r.put((String) "msg", msg);
    return r;
}
public static R ok(Object object) {
    R r = new R();
    r.put("result", object);
    return r;
}
public static R ok(int code, String msg) {
    R r = new R();
    r.put((String) "code", code);
    r.put((String) "msg", msg);
    return r;
}
public static R ok(Map&lt;String, Object&gt; map) {
    R r = new R();
    r.putAll(map);
    return r;
}
public static R ok() {
    return new R();
}
public R put(String key, Object value) {
    super.put(key, value);
    return this;
}
`

}

编写自定义异常(用于后续业务抛出异常错误)

text 复制代码
public class RRException extends RuntimeException {

private static final long serialVersionUID = 1L;

private String msg;

private int code = 500;

public RRException(String msg) {

super(msg);

this.msg = msg;

}

public RRException(String msg, Throwable e) {

super(msg, e);

this.msg = msg;

}

public RRException(String msg, int code) {

super(msg);

this.msg = msg;

this.code = code;

}

public RRException(String msg, int code, Throwable e) {

super(msg, e);

this.msg = msg;

this.code = code;

}

public String getMsg() {

return msg;

}

public void setMsg(String msg) {

this.msg = msg;

}

public int getCode() {

return code;

}

public void setCode(int code) {

this.code = code;

}

}

当访问/user/add这个post接口时,如果参数不符合Model中定义的话,程序中就回抛出400异常状态码,并提示错误信息,如下所示。

text 复制代码
{

"timestamp": "2021-05-20T01:08:28.831+0000",

"status": 400,

"error": "Bad Request",

"errors": [

{

"codes": [ "Size.sysUser.mobile", "Size.mobile", "Size.java.lang.String", "Size" ],

"arguments": [ { "codes": [ "sysUser.mobile", "mobile" ], "arguments": null, "defaultMessage": "mobile", "code": "mobile" },

11,

11

],

"defaultMessage": "手机号长度不对",

"objectName": "sysUser",

"field": "mobile",

"rejectedValue": "155833013",

"bindingFailure": false,

"code": "Size"

}

],

"message": "Validation failed for object='sysUser'. Error count: 1",

"path": "/user/add"

}

自定义校验注解

虽然 JSR303 和 Hibernate Validtor 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。

下面以"List数组中不能含有null元素"为实例自定义校验注解

1、注解定义如示。

text 复制代码
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})

@Retention(RUNTIME)

@Documented

@Constraint(validatedBy = ListNotHaveNullValidatorImpl.class)//此处指定了注解的实现类

public @interface ListNotHaveNull {

/**

* 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义

/  
int value() default 0;  
String message() default "List集合中不能含有null元素";  
Class<?>[] groups() default {};  
Class<? extends Payload>[] payload() default {};  
/*

* 定义List,为了让Bean的一个属性上可以添加多套规则

*/

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})

@Retention(RUNTIME)

@Documented

@interface List {

ListNotHaveNull[] value();

}

}

编写自定义校验实现类

text 复制代码
@Service

public class ListNotHaveNullValidatorImpl implements ConstraintValidator<ListNotHaveNull, List> {

private int value;

@Override

public void initialize(ListNotHaveNull constraintAnnotation) {

//传入value 值,可以在校验中使用

this.value = constraintAnnotation.value();

}

public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {

for (Object object : list) {

if (object == null) {

//如果List集合中含有Null元素,校验失败

return false;

} else if (object instanceof String) {

String value = object.toString();

if (value.trim().length() == 0){

return false;

}

}

}

return true;

}

}

model中添加注解:

text 复制代码
@Data

public class SysRole {
`private Long roleId;

@NotBlank(message = "角色名不能为空")
private String name;

@NotEmpty(message = "资源列表不能为空")
@ListNotHaveNull(message = "List 中不能含有null元素")
@Valid
private List&lt;String&gt; paths;
`

}

编写前端控制器

text 复制代码
@PostMapping("/addRole")

public R addRole(@RequestBody @Valid SysRole sysRole) {

System.out.println("走到这里说明校验成功");

System.out.println(sysRole);

return R.ok(R.SUCCESS_MSG);

}

使用方法同 "简单校验",在在需要校验的Model上面加上@Valid即可。

通用的Validtor校验工具类

text 复制代码
public class ValidatorUtils {

private ValidatorUtils() { }
`private static Validator validator;
static {
    validator = Validation.buildDefaultValidatorFactory().getValidator();
}

/**
 * 校验对象
 *
 * @param object 待校验对象
 * @param groups 待校验的组
 * @throws RRException 校验不通过,则报RRException异常
 */
public static void validateEntity(Object object, Class&lt;?&gt;... groups) throws RRException {
    Set&lt;ConstraintViolation&lt;Object&gt;&gt; constraintViolations = validator.validate(object, groups);

    if (!constraintViolations.isEmpty()) {
        Iterator&lt;ConstraintViolation&lt;Object&gt;&gt; iterator = constraintViolations.iterator();
        StringBuilder msg = new StringBuilder();
        while (iterator.hasNext()) {
            ConstraintViolation&lt;Object&gt; constraint = iterator.next();
            msg.append(constraint.getMessage()).append(',');
        }
        throw new RRException(msg.toString().substring(0,msg.toString().lastIndexOf(',')));
    }
}
`

}

使用方式,在接收到前端传递的参数后,使用ValidatorUtils.validateEntity(【参数名】);即可校验,支持分组校验,分组需要定义分组接口。

相关推荐
打工的小王1 小时前
java并发编程(七)ReentrantReadWriteLock
java·开发语言
lang201509281 小时前
Java并发革命:JSR-133深度解析
java·开发语言
禹凕1 小时前
Python编程——进阶知识(面向对象编程OOP)
开发语言·python
木非哲1 小时前
机器学习--从“三个臭皮匠”到 XGBoost:揭秘 Boosting 算法的“填坑”艺术
算法·机器学习·boosting
abluckyboy1 小时前
基于 Java Socket 实现多人聊天室系统(附完整源码)
java·开发语言
Re.不晚1 小时前
JAVA进阶之路——数据结构之线性表(顺序表、链表)
java·数据结构·链表
毅炼1 小时前
Java 基础常见问题总结(3)
java·开发语言
小辉同志1 小时前
437. 路径总和 III
算法·深度优先·广度优先
一晌小贪欢1 小时前
深入理解 Python HTTP 请求:从基础到高级实战指南
开发语言·网络·python·网络协议·http
七牛云行业应用1 小时前
1M上下文腐烂?实测Opus 4.6 vs GPT-5.3及MoA降本架构源码
人工智能·python·llm·架构设计·gpt-5·claude-opus