青海长云暗雪山,孤城遥望玉门关。——王昌龄《从军行七首·其四》
「壹」JSR303
本章我们进入Zero中的另外一大亮点——对JSR303的支持;JSR303是Java EE 6开始出现的一项子规范,又称为Bean Validation ,它提供了后端执行请求校验的基本规范,而常用的一个该规范的实现就是Hibernate Validator
,Zero中则使用了它来实现该规范,并且对之进行了深度扩展。
接下来的讲解中,我们将忽略控制台中大部分输出,而关注异常响应 部分来看Zero中对JSR部分的支持。Zero对JSR303支持的使用场景如下:
JSR303可以在Zero中的Agent组件中使用。
扩展了JSR303过后,Zero可针对Json结构的数据进行验证(配合yaml配置文件)。
在Zero中一个标准的Verticle组件分两种:Agent组件和Worker组件,后续章节会逐步讲解异步模式下的开发,让读者逐渐了解这两种组件。
关于Zero中对JSR303的部分有两个限制:
在对Json结构的数据执行验证时,验证的数据规范和zero-ui
前端一致,一方面可以和它无缝集成,另外一方面可以独立使用。
JSR303在执行Json结构数据验证时,需配合yml
格式的接口描述文件执行验证定义描述。
1.1. JSR303注解
JSR303中的注解如下:
注解位于包javax.validation.constraints
中。
1.2. 环境准备
1.2.1. Hibernate Validator基础配置
Hibernate Validator的默认配置文件位于资源文件ValidationMessages.properties
,在Zero中,默认的文件名修改成vertx-validation.properties
,同样支持国际化。在Maven结构项目中,仅需在src/main/resources
目录中引入vertx-validation.properties
配置文件配置验证过程中的文字说明。
如果要设置验证信息,则可在资源目录中添加属性文件vertx-validation.properties
,而该文件在处理中文编码时需注意:
如果编辑器使用的编码是UTF-8
(推荐),则可直接在.properties
中输入中文。
如果编辑器使用的编码是ISO-8859-1
(系统默认),则使用native2ascii
工具直接将中文字符串转换成Unicode格式。
1.2.2. 响应规范
Zero中严格遵循HTTP应用层协议,当验证失败时,会生成HTTP状态代码为400(Bad Request)的错误响应,该响应的基础格式如:
Copy {
"code" : "" ,
"message" : "" ,
"info" : ""
}
三节点的含义如下:
1.3. JSR303示例
1.3.1. @Null/@NotNull
必填和可选专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . NotNull ;
import jakarta . validation . constraints . Null ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . POST ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class NullableAgent {
/*
* HTTP 方法为GET,提供参数则抛异常
* 参数位于 `/api/jsr303/null?name=`位置
*/
@ GET
@ Path ( "/null" )
public String nullNo (@ Null @ QueryParam ( "name" ) final String name) {
return "Hi, Null: " + name;
}
/* 对应 vertx-validation.properties 文件中的 nullable.null 值!*/
@ GET
@ Path ( "/null-msg" )
public String nullMsg (@ Null (message = "{nullable.null}" )
@ QueryParam ( "name" ) final String name) {
return "Hi, Null: " + name;
}
/*
* HTTP 方法为GET,不提供参数则抛异常
* 参数位于 `/api/jsr303/notnull?name=`位置
*/
@ POST
@ Path ( "/notnull" )
public String notnull (@ NotNull @ QueryParam ( "name" ) final String name) {
return "Hi, Value: " + name;
}
/* 对应 vertx-validation.properties 文件中的 nullable.notnull 值!*/
@ POST
@ Path ( "/notnull-msg" )
public String notnullMsg (@ NotNull (message = "{nullable.notnull}" )
@ QueryParam ( "name" ) final String name) {
return "Hi, Value: " + name;
}
}
您可以在up-apollo 项目中找到该源代码,上述示例中对应的配置文件内容如下:
Copy nullable.null= 对不起参数`name`必须是null!
nullable.notnull= 对不起参数`name`是必须参数!
发送下边请求:
Copy /hi/jsr303/null-msg?name =lang
您就可以得到一个HTTP状态代码为400(Bad Request)的响应:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) ..." ,
"info" : "对不起参数`name`必须是null!"
}
有关JSR303和Hibernate-Validator的信息读者可以参考相关教程,这里就不重述了,但后续示例会对JSR303的基础注解提供部分参考代码。
1.3.2. @AssertTrue/@AssertFalse
布尔类型专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . AssertFalse ;
import jakarta . validation . constraints . AssertTrue ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class AssertAgent {
@ Path ( "assert" )
@ GET
public String sayBoolean (
@ AssertTrue @ QueryParam ( "male" ) final Boolean isMale ,
@ AssertFalse @ QueryParam ( "female" ) final Boolean isFemale) {
return "Hi, Lang, the parameters is 'male' = " + isMale +
", 'female' = " + isFemale;
}
}
发送下边请求:
于是您将得到如下响应:
Copy {
"data" : "Hi, Lang, the parameters is 'male' = null, 'female' = null"
}
也许读者会困惑,为什么看起来验证并没有生效呢?主要是该参数并没有配合@NotNull
来验证必填性质。Zero中默认是所有参数都可选的,即Optional,当您要求单个参数必填时,则需进一步执行@NotNull限定,不仅仅是@AssertTrue,@AssertFalse的注解,其他所有JSR303的基础注解都依赖@NotNull限定来区分参数的** 必填和 可选**。
加入了@NotNull
过后,发送请求:
Copy /hi/jsr303/assert?male = true & female = true
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must be false"
}
1.3.3. @Min/@Max
数值类型专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . Max ;
import jakarta . validation . constraints . Min ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class NumericAgent {
@ Path ( "numeric" )
@ GET
public String sayNum (
@ Min ( 10 ) @ Max ( 100 )
@ QueryParam ( "age" ) final Integer age ,
@ Min ( 1 )
@ QueryParam ( "test" ) final Integer test
) {
return "Hello, please check your age. " + age;
}
}
发送下边请求:
Copy /hi/jsr303/numeric?age =8 & test = 101
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must be greater than or equal to 10"
}
1.3.4. @DecimalMin/@DecimalMax
浮点类型专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . DecimalMax ;
import jakarta . validation . constraints . DecimalMin ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class DecimalAgent {
@ Path ( "decimal" )
@ GET
public String sayDecimal (
@ DecimalMin ( "0.3" )
@ QueryParam ( "min" ) final Double min ,
@ DecimalMax ( "0.7" )
@ QueryParam ( "max" ) final Double max
) {
return "Hi, min = " + min + ", max = " + max;
}
}
发送下边请求:
Copy /hi/jsr303/decimal?min =0.1 & max = 0.8
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must be less than or equal to 0.7"
}
注:这两个注解的字面量使用的是java.lang.String
类型,而并不是java.lang.Double
类型,简言之,只要字面量可转换成合法浮点数,那么就可以设置到注解的value
中。
1.3.5. @Size
字符串专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . Size ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class SizeAgent {
@ Path ( "size" )
@ GET
public String saySize (
@ Size (min = 1 , max = 20 )
@ QueryParam ( "size" ) final String size
) {
return "Hi, Size = " + size;
}
}
发送下边请求:
Copy /hi/jsr303/size?size =silentbalanceyh@126.com
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "size must be between 1 and 20"
}
1.3.6. @Digits
浮点数精度专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . Digits ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class DigitAgent {
@ Path ( "digit" )
@ GET
public String sayDigit (
@ Digits (integer = 2 , fraction = 2 )
@ QueryParam ( "digit" ) final Double currency
) {
return "Hi, Currency is " + currency;
}
}
发送下边请求:
Copy /hi/jsr303/digit?digit =140.22
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "numeric value out of bounds (<2 digits>.<2 digits> expected)"
}
1.3.7. @Future/@Post
时间专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . Future ;
import jakarta . validation . constraints . Past ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
import java . util . Date ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class DateAgent {
@ Path ( "date" )
@ GET
public String sayDate (
@ Future
@ QueryParam ( "to" ) final Date future ,
@ Past
@ QueryParam ( "from" ) final Date past
) {
return "Hi, Future = " + future + ", Past = " + past;
}
}
发送下边请求:
Copy hi/jsr303/date?to =2018-09-11 & from = 2018 -04-01
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must be a future date"
}
1.3.8. @Pattern
正则表达式专用注解,参考下边示例:
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . validation . constraints . Email ;
import jakarta . validation . constraints . Pattern ;
import jakarta . ws . rs . GET ;
import jakarta . ws . rs . Path ;
import jakarta . ws . rs . QueryParam ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class PatternAgent {
@ Path ( "pattern" )
@ GET
public String sayRegex (
@ Pattern (regexp = "^$|^[a-zA-Z]+$" ,
message = "must be a letter " )
@ QueryParam ( "pattern" ) final String size
) {
return "Hi, Size = " + size;
}
@ Path ( "email" )
@ GET
public String sayEmail (
@ Email
@ QueryParam ( "email" ) final String email
) {
return "Hi, email = " + email;
}
}
发送下边请求:
Copy hi/jsr303/pattern?pattern =1017
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must be a letter "
}
该示例中不仅使用了JSR303原生的@Pattern
注解,还使用了Hibernate Validator中扩展的JSR303的相关注解,它扩展的常用注解如下:
详细用法参考Hibernate Validator官方文档。
1.3.9. Pojo模式
除了上边的单独参数的校验以外,Zero中同样借着Hibernate Validator支持Pojo类型的Java类的校验,参考下边示例代码:
JavaJson类
Copy package cn . vertxup . micro . jsr303 . agent ;
import lombok . Data ;
import jakarta . validation . constraints . Email ;
import jakarta . validation . constraints . Min ;
import jakarta . validation . constraints . NotNull ;
@ Data
public class JavaJson {
@ NotNull
private String name;
@ Email
private String email;
@ Min ( 1 )
private Integer age;
}
PojoAgent主类
Copy package cn . vertxup . micro . jsr303 . agent ;
import io . vertx . core . json . JsonObject ;
import io . vertx . up . annotations . EndPoint ;
import io . vertx . up . util . Ut ;
import javax . validation . Valid ;
import jakarta . ws . rs . BodyParam ;
import jakarta . ws . rs . POST ;
import jakarta . ws . rs . Path ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class PojoAgent {
@ Path ( "pojo" )
@ POST
public JsonObject sayPojo (
@ BodyParam @ Valid final JavaJson json
) {
return Ut . serializeJson (json);
}
}
发送下边请求:
请求内容如:
Copy {
"email" : "lang.yu@hpe.com"
}
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60000 ,
"message" : "[ERR-60000] (Validator) Web Exception occurs: (400) - ..." ,
"info" : "must not be null"
}
倘若您直接改成下边请求就可以发送成功了:
Copy {
"name" : "Lang" ,
"email" : "lang.yu@126.com" ,
"age" : 33
}
该示例中使用了lombok库操作Pojo,并且使用了Zero对JSR303的扩展注解@BodyParam。
「贰」JSR303扩展
2.1. 现存问题
在开发企业级项目过程中,JSR303的使用也许远远不够,它主要可以解决下边两个场景的使用:
但是还有很多场景可能无法满足某些基本需求,例如:
无固定Pojo结构的复杂参数验证,如JsonObject/JsonArray
。
2.2. Zero扩展
Zero框架对JSR303进行了实现层的扩展,这些扩展让Zero在处理前文提到的问题时羽翼更加丰富了,关于入参的注解此处就不详细描述了,这部分在前边一个章节已经提及。
2.2.1. 启用配置
本章节会使用一个特殊的Zero注解:io.vertx.up.annotations.Codex
,该注解表示当前结构会启用Zero中的扩展验证功能。启用了@Codex
注解过后,需要在项目的资源目录中提供验证配置文件:
配置文件目录:src/main/resources/codex/
文件名基础规则:
基本文件名 = <api>.<method>.yml
。
api中如果出现了:name
路径参数,则:
被替换成$
符号而生效。
在对应目录中创建示例中所需的验证规则文件hi.jsr303.advanced.post.yml
:
该文件的内容如下:
Copy username :
- type : required
message : "Please input your username!"
- type : length
min : 6
message : "Your username length must be greater than 6"
password :
- type : required
message : "Please provide your password"
2.2.2. 示例代码
参考下边的示例代码:
Copy package cn . vertxup . micro . jsr303 . extension ;
import io . vertx . core . json . JsonObject ;
import io . vertx . up . annotations . Codex ;
import io . vertx . up . annotations . EndPoint ;
import jakarta . ws . rs . BodyParam ;
import jakarta . ws . rs . POST ;
import jakarta . ws . rs . Path ;
@ EndPoint
@ Path ( "/hi/jsr303" )
public class BodyAgent {
@ POST
@ Path ( "/advanced" )
public JsonObject testCodex (
@ BodyParam @ Codex final JsonObject user
) {
return user;
}
}
发送下边请求:
请求内容如:
Copy {
"username" : "Lang"
}
您就得到了400 Bad Request的响应信息:
Copy {
"code" : -60005 ,
"message" : "[ERR-60005] (MinLengthRuler) Web Exception occurs: (400) - ..." ,
"info" : "Your username length must be greater than 6"
}
2.3. Zero功能支持
上述小节提供了基本的示例让读者对@Codex
有了一定的了解,那么Zero对这种验证究竟扩展到何等程度呢——我相信这是读者最关心的。
2.3.1. 参数类型
细心的读者会发现,@Codex只会作用于部分复杂结构,这种复杂结构主要包括:
简单说上述类型作为参数时就可以直接使用@Codex注解。
2.3.2. 规则分类
上述文件hi.jsr303.advanced.post.yml
中为不同的字段定义了验证规则,字段的验证规则可支持多个,验证时按照定义顺序依次验证。
每种类型的公共配置如:
Zero中支持的类型表如下:
「叄」总结
本章主要介绍了Zero对JSR303规范的支持、扩展和相关示例:
Zero支持JSR303规范中的大部分验证注解(Hibernate-Validator实现)。
Zero对JSR303规范进行了扩展,支持复杂数据结构的验证。