前言
由于Dubbo默认的JSR303参数校验在未通过校验时,所返回的结果并没有实现序列化,导致消费者调用服务时报错。且项目对于服务调用结果使用统一的Result对象,因此Dubbo默认的参数校验无法满足项目要求,故对Dubbo的参数校验进行改造。
Dubbo参数校验切入点
Dubbo使用org.apache.dubbo.rpc.Filter
进行参数校验,具体实现类为org.apache.dubbo.validation.filter.ValidationFilter
,并在里面捕获参数校验出错的异常javax.validation.ValidationException
封装后返回给服务消费者。因此我们自定义了CustomValidationFilter
以替换Dubbo的ValidationFilter
。
由于Dubbo的参数异常封装类org.hibernate.validator.internal.engine.ConstraintViolationImpl
没有实现序列化,且对象较大,浪费网络IO资源。因此我们对ConstraintViolationImpl进行简化并实现序列化接口,自定义了CustomConstraintViolation
用于封装返回给消费者的参数异常信息。
参数校验过滤器CustomValidationFilter实现
主要是捕获ConstraintViolationException异常,并封装成Result对象返回,具体如下代码注释所示。
/**
* 描述:参数校验过滤器,改写自ValidationFilter,
* 该类捕获ConstraintViolationException异常,并封装成Result对象返回
*
* @author: xhsf
* @create: 2020/10/30 15:05
*/
@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000)
public class CustomValidationFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(CustomValidationFilter.class);
private Validation validation;
/**
* Sets the validation instance for ValidationFilter
* @param validation Validation instance injected by dubbo framework based on "validation" attribute value.
*/
public void setValidation(Validation validation) {
this.validation = validation;
}
/**
* Perform the validation of before invoking the actual method based on <b>validation</b> attribute value.
* @param invoker service
* @param invocation invocation.
* @return Method invocation result
* @throws RpcException Throws RpcException if validation failed or any other runtime exception occurred.
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (validation != null && !invocation.getMethodName().startsWith("$")
&& ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) {
try {
Validator validator = validation.getValidator(invoker.getUrl());
if (validator != null) {
validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
}
} catch (RpcException e) {
throw e;
}
// 添加catch ConstraintViolationException用于实现自定义的参数校验异常处理逻辑
catch (ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
logger.info("校验错误详情:{}", constraintViolations);
// 把Set<ConstraintViolation<?>>转换成List<CustomConstraintViolation>
final List<CustomConstraintViolation> collect = constraintViolations.stream()
.map(CustomConstraintViolation::buildCustomConstraintViolation)
.collect(Collectors.toList());
return AsyncRpcResult.newDefaultAsyncResult(
com.xiaohuashifu.recruit.common.result.Result.fail(
ErrorCode.INVALID_PARAMETER, collect), invocation);
} catch (ValidationException e) {
return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);
} catch (Throwable t) {
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
}
return invoker.invoke(invocation);
}
}
参数校验异常信息封装类CustomConstraintViolation实现
该类较为简单,只是封装需要的参数校验异常信息,如下代码所示。
/**
* 描述:自定义的异常信息封装类
*
* @author: xhsf
* @create: 2020/11/15 17:20
*/
public class CustomConstraintViolation implements Serializable {
private String interpolatedMessage;
private String propertyPath;
private String rootBeanClass;
public CustomConstraintViolation(String interpolatedMessage, String propertyPath, String rootBeanClass) {
this.interpolatedMessage = interpolatedMessage;
this.propertyPath = propertyPath;
this.rootBeanClass = rootBeanClass;
}
public static CustomConstraintViolation buildCustomConstraintViolation(ConstraintViolation<?> constraintViolation) {
return new CustomConstraintViolation(constraintViolation.getMessage(),
constraintViolation.getPropertyPath().toString(),
constraintViolation.getRootBeanClass().getName());
}
// gettes and settes
}
替换默认ValidationFilter为CustomValidationFilter
application.properties配置文件
# 把默认参数校验过滤器改成自定义的
dubbo.provider.filter=-validation, customValidationFilter
配置resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
添加以下配置
customValidationFilter=com.xiaohuashifu.recruit.common.filter.CustomValidationFilter
更新于2020-12-02
之前觉得应该返回参数校验的详细信息,但是最近思考后觉得详细的错误信息并没必要,只需要提供第一个错误信息即可。因此 CustomValidationFilter
类修改如下,并删除 CustomConstraintViolation
。
/**
* 描述:参数校验过滤器,改写自ValidationFilter,
* 该类捕获ConstraintViolationException异常,并封装成Result对象返回
*
* @author: xhsf
* @create: 2020/10/30 15:05
*/
@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000)
public class CustomValidationFilter implements Filter {
private Validation validation;
/**
* Sets the validation instance for ValidationFilter
* @param validation Validation instance injected by dubbo framework based on "validation" attribute value.
*/
public void setValidation(Validation validation) {
this.validation = validation;
}
/**
* Perform the validation of before invoking the actual method based on <b>validation</b> attribute value.
* @param invoker service
* @param invocation invocation.
* @return Method invocation result
* @throws RpcException Throws RpcException if validation failed or any other runtime exception occurred.
*/
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (validation != null && !invocation.getMethodName().startsWith("$")
&& ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), VALIDATION_KEY))) {
try {
Validator validator = validation.getValidator(invoker.getUrl());
if (validator != null) {
validator.validate(invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
}
} catch (RpcException e) {
throw e;
}
// 添加catch ConstraintViolationException用于实现自定义的参数校验异常处理逻辑
catch (ConstraintViolationException e) {
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
ConstraintViolation<?> firsConstraintViolation = constraintViolations.iterator().next();
return AsyncRpcResult.newDefaultAsyncResult(
com.xiaohuashifu.recruit.common.result.Result.fail(
ErrorCodeEnum.INVALID_PARAMETER, firsConstraintViolation.getMessage()), invocation);
} catch (ValidationException e) {
return AsyncRpcResult.newDefaultAsyncResult(new ValidationException(e.getMessage()), invocation);
} catch (Throwable t) {
return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
}
}
return invoker.invoke(invocation);
}
}