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(【参数名】);即可校验,支持分组校验,分组需要定义分组接口。

相关推荐
Ning_.5 分钟前
力扣第116题:填充每个节点的下一个右侧节点指针 - C语言解法
c语言·算法·leetcode
yangjiwei02076 分钟前
数据结构-排序
数据结构·python
V+zmm101349 分钟前
社区二手物品交易小程序ssm+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
小小unicorn11 分钟前
第二章:算法练习题2
算法
坊钰11 分钟前
【Java 数据结构】合并两个有序链表
java·开发语言·数据结构·学习·链表
抓住鼹鼠不撒手12 分钟前
力扣 429 场周赛-前两题
数据结构·算法·leetcode
秋天下着雨18 分钟前
apifox调用jar程序
java·python·jar
bs_10121 分钟前
【保姆式】python调用api通过机器人发送文件到飞书指定群聊
python·机器人·飞书
m0_7482510822 分钟前
docker安装nginx,docker部署vue前端,以及docker部署java的jar部署
java·前端·docker
A227424 分钟前
Redis——缓存雪崩
java·redis·缓存