JSR380 验证规范
介绍
业务框架支持 JSR380 验证规范 (由 hibernate-validator 支持),该规范可以使得业务代码更加的干净
若不使用验证框架,常规的做法是不断的在业务代码中疯狂使用 if else 输出,这将造成业务代码混乱。 下面,我们使用两个示例对比来说明 JSR380 的优势。 一个是使用了 JSR380 的示例,一个是没有使用的示例。
Example Source Code
see https://github.com/iohao/ionet-examples
path : ionet-cookbook-code
- Jsr380Action
没有使用验证的示例
现在先让我们看一个没有使用 JSR380 的示例。 在这个示例中,我们对电子邮箱和年龄这两个属性的数据进行合法验证,验证包括邮箱的格式和年龄的最小范围。
message 的值如下
message.email="a";
message.age = 1;
从示例中可以看出,这里只有两个字段需要验证,就有很多代码了。 如果在多加几个需要验证的字段或多加几个字段的验证规则,那么代码就会非常的多, 这些手动验证的代码大部分是重复。
- code 5~10: 验证 email。
- code 12~13: 验证 age。
@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(ValidMessage message) {
String email = message.email;
ErrorCode.emailChecked.assertNullThrows(email == null);
Pattern pattern = Pattern.compile("[a-zA-Z0-9_-]+@\\w+\\.[a-z]+(\\.[a-z]+)?");
var result = pattern.matcher(email).find()
ErrorCode.emailChecked.assertTrue(result);
int age = message.age;
ErrorCode.ageChecked.assertTrueThrows(age < 2);
// Your biz code
...
}
}
@ToString
@ProtobufClass
public class ValidMessage {
public String email;
public int age;
}
@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public enum ErrorCode implements ErrorInformation {
emailChecked(100, "Email error."),
ageChecked(101, "Age error."),
;
final int code;
final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
关于这些验证问题,在 JSR380 中已经给出了统一的解决方案。 大部分的框架支持了这一规范,所以我们的业务框架也是支持的。
如何安装
JSR380 是一个按需使用的功能,所以需要在你项目的 maven 中引入以下依赖。
<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version>
</dependency>
<!-- hibernate validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>9.0.1.Final</version>
</dependency>
<!-- EL implementation. In a Java SE environment, you must add the implementation as a dependency to your POM file -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>
如何使用
开启验证
class OneApplication {
static void main() {
CoreGlobalConfig.setting.validator = true;
...
new RunOne()
...
.startup();
}
}
我们给 ValidMessage 加上了一些验证相关的注解,这些注解可以帮助我们简化验证。
@ToString
@ProtobufClass
public class ValidMessage {
@NotNull
@Email
public String email;
@Min(value = 2, message = "Age error")
public int age;
}
action example
同样的,在这个示例中我们对电子邮箱和年龄这两个属性的数据进行合法验证, 验证包括邮箱的格式和年龄的最小范围。
我们在方法参数中添加了一个 @Valid 注解,该注解是告诉框架这个参数需要做验证。
@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(@Valid ValidMessage message) {
// Your biz code
...
}
}
代码中没有任何的逻辑(干净的业务体),因为验证相关的逻辑全都由业务框架来做了,包括错误码的响应。 这段代码与上面没有使用 JSR380 的示例代码达到同等的验证效果。 除了可以达到同等验证效果的同时,业务框架还做了一些小优化。 如果业务参数验证没有通过,就不会执行 action。
业务框架结合了 JSR380 和异常机制,使得程序员只需关注真正的业务。
验证失败的打印预览
下面分别测试了 email 和 age,当验证失败时,框架会把这个错误码和错误提示给到请求端。
控制台打印
- CN
- EN
┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:9312241] ━━━━
┣ userId: 1378604058
┣ 参数: ValidMessage(email=luoyizhu@gmail.com, age=1)
┣ 错误码: -1001
┣ 错误消息: age Age error
┣ 耗时: 0 ms
┗━━ [ionet:25.1] ━━ [线程:User-8-3] ━━ [连接方式:WebSocket] ━━ [traceId:1761983850217] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:9312241] ━━━━
┣ userId: 1378604058
┣ 参数: ValidMessage(email=luoyizhu, age=99)
┣ 错误码: -1001
┣ 错误消息: email 不是一个合法的电子邮件地址
┣ 耗时: 0 ms
┗━━ [ionet:25.1] ━━ [线程:User-8-3] ━━ [连接方式:WebSocket] ━━ [traceId:1761983850218] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:6468190] ━━━━
┣ userId: 1378604058
┣ RequestParam: ValidMessage(email=luoyizhu@gmail.com, age=1)
┣ ErrorCode: -1001
┣ ErrorMessage: age Age error
┣ ExecutionTime: 0 ms
┗━━ [ionet:25.1] ━━ [Thread:User-8-3] ━━ [ConnectionWay:WebSocket] ━━ [traceId:1761984090278] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:6468190] ━━━━
┣ userId: 1378604058
┣ RequestParam: ValidMessage(email=luoyizhu, age=99)
┣ ErrorCode: -1001
┣ ErrorMessage: email must be a well-formed email address
┣ ExecutionTime: 0 ms
┗━━ [ionet:25.1] ━━ [Thread:User-8-3] ━━ [ConnectionWay:WebSocket] ━━ [traceId:1761984090279] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
自动验证
默认情况下,我们需要手动写入 @Valid 注解来告诉框架这个参数需要验证。 此外,框架还提供了自动验证的设置,这样就不需要在每个参数中手动配置了。
@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(@Valid ValidMessage message) {
// Your biz code
...
}
}
开启自动验证后,参数不需要添加 @Valid。
class OneApplication {
static void main() {
CoreGlobalConfig.setting.validator = true;
CoreGlobalConfig.setting.validatorAutoCall = true;
...
new RunOne()
...
.startup();
}
}
@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(ValidMessage message) {
// Your biz code
...
}
}
如何选择?
个人倾向手动写,因为这在语义上更清晰,之所以提供自动验证是为了兼容 ioGame 的代码,