Validator
作为一个web服务对参数的校验是必不可少的,这节我们将介绍Hibernate Validator 在Quarkus中的使用。
本章目标
- 校验方式
- 接口参数校验
- 方法参数校验
- 参数校验扩展
1 搭建项目
项目依赖:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
2 校验方式
Quarkus 提供了两种校验方式
- 通过注入校验工具对象,手动校验参数
Validator
: 关于Java中的Validator,大家应该不会陌生它是Java原生提供的。Hibernate Validator 只是对它进行扩展。不了解的同学可以自行了解一下。 - 通过注解修饰需要校验的对象自动校验
@Valid
同上
我们本章以对Json请求的方式的校验,单个参数的校验更简单,直接使用对应的校验注解即可。
定义VO类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Fruit {
@NotBlank(message = "name not null")
public String name;
@NotBlank(message = "description not null")
public String description;
}
我这里用了Lombok插件,通过注解省略了构造方法和Getter&Setter方法。同时将常用的校验注解贴出来:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
同时校验注解也支持自定义实现,有需求的同学可自行百度。
3 手动校验
上代码:
@Slf4j
@Path("/fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {
@Inject
Validator validator;
private final Map<String, Fruit> fruits = new HashMap<>();
public FruitResource() {
fruits.put("Apple", new Fruit("Apple", "Winter fruit"));
fruits.put("Pineapple", new Fruit("Pineapple", "Tropical fruit"));
}
@GET
public Map<String, Fruit> list() {
return fruits;
}
@PUT
public Map<String, Fruit> edit(Fruit fruit) {
Set<ConstraintViolation<Fruit>> violationSet = validator.validate(fruit);
if(!violationSet.isEmpty()){
String message = violationSet.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
log.error("param validate error {}", message);
return fruits;
}
Fruit old = fruits.get(fruit.getName());
old.setDescription(fruit.getDescription());
return fruits;
}
@POST
public Map<String, Fruit> add(@Valid Fruit fruit) {
fruits.put(fruit.getName(), fruit);
return fruits;
}
@DELETE
public Map<String, Fruit> delete(Fruit fruit) {
fruits.remove(fruit.getName());
return fruits;
}
}
这里简单实现了一个Fruit的Rest CRUD, 手动校验看edit方法, 使用validator.validate(value)
方法即可。但是需要注意的是需要手动处理校验结果。如果不处理的话相当于这个校验是无效的。在上面的代码中只是将校验信息log一下,更优雅一点的话可以通过抛出异常,在全局异常中统一处理。
测试一下:
4 自动校验
代码同上,这次测试add 方法:
这次使用注解,框架是已经做了处理了,直接将校验异常信息返回了,看起来还是比较清晰明了的。相比之下是不是注解的自动校验方式更好一点呢。
5 方法参数校验
在服务的方法中校验参数跟接口的校验是一样的,并没有太大的区别,这里思考一下,如果我们的需求是按我们的响应格式返回结果怎么办。
通过捕获异常发现,当参数校验失败时抛出的异常是ConstraintViolationException
所以思路就是提供一个全局异常的Handler 处理一下。全局异常我们上节应该已经讲过了。
@Slf4j
public class GlobalExceptionHandler {
@ServerExceptionMapper
public Response globalExceptionHandler(Exception e){
log.error("global exception ", e);
return Response.status(500).entity("SYSTEM ERROR").build();
}
@ServerExceptionMapper(ConstraintViolationException.class)
public Response ConstraintViolationExceptionHandler(ConstraintViolationException e){
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(","));
log.error("param validate error {}", message);
HashMap<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("msg", "参数校验失败");
result.put("data", message);
return Response.status(400).entity(result).build();
}
}
6 参数校验扩展
Validator 本身是Java 提供的扩展包,Hibernate Validator 进行扩展的,Quarkus只是将他们集成进框架。同样留给我们自定的扩展空间。Quarkus 将 Validator 与 CDI进行了集成我们只需要按需实现我们自己的实现即可。
javax.validation.ClockProvider
javax.validation.ConstraintValidator
javax.validation.ConstraintValidatorFactory
javax.validation.MessageInterpolator
javax.validation.ParameterNameProvider
javax.validation.TraversableResolver
org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy
org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory
但是官网有这么一句提示Obviously, for each listed type, you can declare only one bean.These beans should be declared as @ApplicationScoped.
就是说没种类型只能声明一个Bean 并且应该用@ApplicationScoped 修饰。 但是实测没有@ApplicationScope 也是可以的。或许我没看懂作者的意思。
定义注解:
@Documented
@Constraint(
validatedBy = StartWithValidator.class
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface StartWith {
String prefix() default "";
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
校验器实现:
@Slf4j
public class StartWithValidator implements ConstraintValidator<StartWith, String> {
private StartWith startWith;
@Override
public boolean isValid(String s, ConstraintValidatorContext context) {
log.info("自定义参数校验");
System.out.println(context.getDefaultConstraintMessageTemplate());
System.out.println(startWith.prefix());
return false;
}
@Override
public void initialize(StartWith constraintAnnotation) {
this.startWith = constraintAnnotation;
}
}
使用
@StartWith(prefix = "__", message = "value must start witch __")
@NotBlank(message = "description not null")
public String description;
结果:
{
"msg": "参数校验失败",
"code": 400,
"data": "value must start witch __"
}
总结
本章主要介绍了在Quarks项目中如何使用Validator 校验 参数,同时 Validator还有更多用法,如校验分组等等都是可以使用的。