(05)Dubbo微服务实战-自定义Dubbo参数校验异常处理逻辑

1,712 阅读2分钟

前言

由于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);
    }

}