本文基于 Dubbo 2.6.1 版本,望知悉。
1. 概述
本文分享 dubbo-filter-validation
项目的 ValidationFilter 过滤器,用于服务消费者和提供者中,提供 参数验证 的功能。在 《Dubbo 用户指南 —— 参数验证》 定义如下:
参数验证功能,是基于 JSR303 Bean Validation 实现的,用户只需标识 JSR303 标准的验证 annotation,并通过声明 filter 来实现验证。
- 配置和示例,官方文档已经写的很齐全,笔者就不多哔哔了。
如下是新版本的 Maven 依赖的例子:
<!-- JSR 303 - Bean Validation begin -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.4.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<!-- JSR 303 - Bean Validation end -->
2. ValidationFilter
com.alibaba.dubbo.validation.filter.ValidationFilter
,实现 Filter 接口,参数验证过滤器实现类。代码如下:
1: (group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.VALIDATION_KEY, order = 10000) |
- 第 17 行:非泛化调用和回音调用等方法。
- 第 18 行:判断方法开启 Validation 功能。因为,一个服务里,可能只有部分方法开启了 Validation 功能。
- 第 21 行:调用
Validation$Adaptive#getValidator(url)
方法,基于 URL 为维度,获得 Validator 对象。 - 第 22 至 25 行:调用
Validator#validate(String methodName, Class<?>[] parameterTypes, Object[] arguments)
方法,方法参数验证。若不合法,抛出异常。 - 第 33 行:调用
Invoker#invoke(invocation)
方法,服务调用。
3. API 定义
3.1 Validator
com.alibaba.dubbo.validation.Validator
,验证器接口。代码如下:
public interface Validator { |
3.2 Validation
com.alibaba.dubbo.validation.Validation
,Validator 工厂接口。代码如下:
"jvalidation") ( |
@SPI("jvalidation")
注解,Dubbo SPI 拓展点,默认为"jvalidation"
。@Adaptive("validation")
注解,基于 Dubbo SPI Adaptive 机制,加载对应的 Validator 实现,使用URL.validation
属性。
3.2.1 AbstractValidation
com.alibaba.dubbo.validation.support.AbstractValidation
,实现 Validation 接口,Validator 工厂抽象类。代码如下:
public abstract class AbstractValidation implements Validation { |
3.3 @MethodValidated
com.alibaba.dubbo.validation.@MethodValidated
,方法分组验证注解。代码如下:
({ElementType.METHOD}) |
- 使用场景:当调用某个方法时,需要检查多个分组,可以在接口方法上加上该注解。
用法:
({Save.class, Update.class})
void relatedQuery(ValidationParameter parameter);- 在接口方法上增加注解,表示
#relatedQuery(ValidationParameter)
这个方法,需要同时检查 Save 和 Update 这两个分组。 - 如果胖友对 Java Bean Validation 分组不熟悉,可以理解起来比较绕。可以跑下官方提供的
com.alibaba.dubbo.config.validation
的立足。relatedQuery
- 在接口方法上增加注解,表示
4. JSR303 实现
基于 JSR303 Bean Validation 实现的,用户只需标识 JSR303 标准的验证 annotation 。
4.1 JValidator
com.alibaba.dubbo.validation.support.jvalidation.JValidator
,实现 Validator 接口,基于 JSR303 的验证器实现类。
4.1.1 构造方法
/** |
"jvalidation"
配置项,可指定具体的 JSR303 的实现类。- 如果我们未配置,并且引入 Hibernate Validator ,则使用的是 HibernateValidatorFactory 。
4.1.2 validate
1: |
- ============ 【第一步】获得验证分组集合 ============
- 第 4 行:验证分组集合
group
,目前有四种来源。 - 【第一种】第 5 至 12 行:添加以方法命名的内部接口,作为验证分组。例如
ValidationService#save(...)
方法,对应ValidationService.Save
接口。 - 【第二种】第 13 至 19 行:添加方法的
@MethodValidated
注解的值对应的类,作为验证分组。 - 【第三种】第 22 行:添加 Default.class 类,作为验证分组。在 JSR 303 中,未设置分组的验证注解,使用 Default.class 。
- 【第四种】第 24 行:添加服务接口类
clazz
,作为验证分组。 - 最终生成的验证分组集合的顺序为:【第三种】》【第四种】》【第一种】》【第二种】。
- ============ 【第二步】验证方法参数 ============
- 第 29 行:验证错误集合
violations
。 - 【第一步】调用
#getMethodParameterBean(Class<?> clazz, Method method, Object[] args)
方法,获得方法参数的 Bean 对象。因为,JSR 303 是 Java Bean Validation ,以 Bean 为维度。具体的实现,我们在 「4.1.3 getMethodParameterBean」 中,详细解析。 - 【第一步】调用
Validator#validate(T object, Class<?>... groups)
方法,验证 Bean 对象。 【第二步】循环方法参数,调用
#validate(violations, arg, classGroups)
方法,验证集合参数。为什么会有这一步?因为,在【第一步】中,校验的是 Constraint 注解的参数( 例如@NotNull
) ,但是呢,若是集合参数,不会校验集合中的每个元素。我们来举个例子:void saves(@NotNull(message = "至少需要保存一个用户") User[] users);
- 【第一步】校验
users
参数的@NotNull
约束。 - 【第二步】校验
users
参数中的每个 User 的约束。
- 【第一步】校验
第 40 至 44 行:若有验证错误,抛出 ConstraintViolationException 异常。
#validate(Set<ConstraintViolation<?>> violations, Object arg, Class<?>... groups)
方法,代码如下:
1: /** |
第 9 行:调用
#isPrimitives(Class<?> cls)
方法,判断是否为基本类型。若是基本类型,已经被【第一步】给验证了,避免重复验证。代码如下:private static boolean isPrimitives(Class<?> cls) {
// [] 数组,使用内部的类来判断
if (cls.isArray()) {
return isPrimitive(cls.getComponentType());
}
// 直接判断
return isPrimitive(cls);
}
private static boolean isPrimitive(Class<?> cls) {
return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class
|| Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls);
}第 10 至 14 行:验证
[ ]
数组参数,循环调用【第 26 至 29】的验证。- 第 15 至 19 行:验证 Collection 参数,循环调用【第 26 至 29】的验证。
- 第 20 至 25 行:验证 Map 参数,循环调用【第 26 至 29】的验证。
- 第 26 至 29 行:验证单个参数。
4.1.3 getMethodParameterBean
在看 #getMethodParameterBean(Class<?> clazz, Method method, Object[] args)
的具体实现代码之前,我们来看下它,根据方法,自动生成 Bean 类的例子。
接口方法,代码如下:
void demo(@NotNull(message = "名字不能为空") @Min(value = 6, message = "昵称不能太短") String name,
String password, // 不校验
@NotNull(message = "至少需要保存一个用户") User user);生成 Bean 类,代码如下:
package com.alibaba.dubbo.demo.DemoService_DemoParameter_java.lang.String_java.lang.String_com.alibaba.dubbo.demo.entity;
public class User {
"名字不能为空") (message =
6, message = "昵称不能太短") (value =
public java.lang.String name;
public java.lang.String password;
"至少需要保存一个用户") (message =
public com.alibaba.dubbo.demo.entity.User user;
public User() {}
}- 😈 Javassist 生成的类,使用 JD-GUI 反编译一直报错。所以,该类是笔者,手工生成的。哈哈哈,意思能达到就好。
#getMethodParameterBean(Class<?> clazz, Method method, Object[] args)
方法,代码如下:
1: /** |
第 13 至 15 行:调用
#hasConstraintParameter(method)
方法,判断是否有 Constraint 注解( 例如,@NotNull
)的方法参数。若没有,则无需创建 Bean 对象。代码如下:private static boolean hasConstraintParameter(Method method) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 循环所有方法参数的注解
if (parameterAnnotations != null && parameterAnnotations.length > 0) {
// 循环每个方法参数的注解数组
for (Annotation[] annotations : parameterAnnotations) {
// 是否有 Constraint 注解
for (Annotation annotation : annotations) {
if (annotation.annotationType().isAnnotationPresent(Constraint.class)) {
return true;
}
}
}
}
return false;
}第 17 至 22 行:获得 Bean 类。
第 23 至 73 行:若 Bean 类不存在,使用 Javassist 动态编译生成。🙂 代码比较简单,已经添加详细注释,胖友耐心看看哈。其中,
#createMemberValue(ConstPool cp, CtClass type, Object value)
方法,获得注解每个属性的值,代码如下:// Copy from javassist.bytecode.annotation.Annotation.createMemberValue(ConstPool, CtClass);
private static MemberValue createMemberValue(ConstPool cp, CtClass type, Object value) throws NotFoundException {
MemberValue memberValue = javassist.bytecode.annotation.Annotation.createMemberValue(cp, type);
if (memberValue instanceof BooleanMemberValue) // Boolean
((BooleanMemberValue) memberValue).setValue((Boolean) value);
else if (memberValue instanceof ByteMemberValue) // Byte
((ByteMemberValue) memberValue).setValue((Byte) value);
else if (memberValue instanceof CharMemberValue) // Char
((CharMemberValue) memberValue).setValue((Character) value);
else if (memberValue instanceof ShortMemberValue) // Short
((ShortMemberValue) memberValue).setValue((Short) value);
else if (memberValue instanceof IntegerMemberValue) // Integer
((IntegerMemberValue) memberValue).setValue((Integer) value);
else if (memberValue instanceof LongMemberValue) // Long
((LongMemberValue) memberValue).setValue((Long) value);
else if (memberValue instanceof FloatMemberValue) // Float
((FloatMemberValue) memberValue).setValue((Float) value);
else if (memberValue instanceof DoubleMemberValue)
((DoubleMemberValue) memberValue).setValue((Double) value);
else if (memberValue instanceof ClassMemberValue) // Class
((ClassMemberValue) memberValue).setValue(((Class<?>) value).getName());
else if (memberValue instanceof StringMemberValue) // String
((StringMemberValue) memberValue).setValue((String) value);
else if (memberValue instanceof EnumMemberValue) // Enum
((EnumMemberValue) memberValue).setValue(((Enum<?>) value).name());
/* else if (memberValue instanceof AnnotationMemberValue) */
else if (memberValue instanceof ArrayMemberValue) { // 数组
CtClass arrayType = type.getComponentType();
int len = Array.getLength(value);
// 循环,递归
MemberValue[] members = new MemberValue[len];
for (int i = 0; i < len; i++) {
members[i] = createMemberValue(cp, arrayType, Array.get(value, i));
}
((ArrayMemberValue) memberValue).setValue(members);
}
return memberValue;
}第 75 至 81 行:创建 Bean 对象,设置 Bean 对象的每个属性的值。
😈 又是一处,使用 Javassist 动态编译类的代码。好用!!!
4.2 JValidation
com.alibaba.dubbo.validation.support.jvalidation.JValidation
,实现 AbstractValidation 抽象类,代码如下:
public class JValidation extends AbstractValidation { |
666. 彩蛋
🙂 美滋滋,终于弄懂,为什么 JSR303 是 Java Bean Validation ,结果接口方法上,每个参数都添加 Constraint 的注解,结果也可以做校验。
推荐两篇文章: