RPC参数校验框架实战:Dubbo + HibernateValidator

 

1.前言

用过SpringMVC的参数校验框架的同学都知道,通过注解的方式,可以优雅的实现Restful接口的参数校验。例如:

    @NotBlank(message = "parameter appId required")
    String appId;

具体可以参考这篇文章:springmvc的数据校验的实现。

那么基于Dubbo框架的RPC接口是否也能使用类似的方式进行参数校验?下文是一种实现方案,供诸君参考。

2.准备工作

1)引入参数校验框架依赖的包

        
        
            javax.el
            javax.el-api
            3.0.0
        
        
            org.glassfish
            javax.el
            3.0.0
        
        
            org.hibernate.validator
            hibernate-validator
            6.0.12.Final
        
        

2)封装几个common类,用于生产者和消费者之间的返回值约定。

2-1)返回值通用类

@Data
public class CommonResponse implements Serializable {
    private T data;
    private Integer code;
    private String message;
}

2-2)返回code枚举

public enum ResponseCode {
    OK(0, "OK"),
    ...
    private Integer code;
    private String message;

    ResponseCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

2-3)通用异常类

public class RpcBizException extends RuntimeException{
    private int code;
    private String message;
    public RpcBizException() {
        super();
    }
    ...
}

3. 自定义Validation

参考文档Dubbo参数验证扩展点

3.1 继承Dubbo的AbstractValidation类

import org.apache.dubbo.common.URL;
import org.apache.dubbo.validation.Validator;
import org.apache.dubbo.validation.support.AbstractValidation;

/**
 * RPC接口自定义业务Validation(用于dubbo参数校验过滤)
 *
 */
public class DubboBizValidation extends AbstractValidation{
    @Override
    protected Validator createValidator(URL url) {
        return new DubboBizValidator(url);
    }
}

3.2 实现Dubbo的Validator接口

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.validation.Validator;
import org.apache.dubbo.validation.support.jvalidation.JValidator;

import xxx.ResponseCode;
import xxx.RpcBizException;
import lombok.extern.slf4j.Slf4j;
/**
 * RPC接口自定义业务Validator(用于dubbo参数校验过滤)
 *
 */
@Slf4j
public class DubboBizValidator implements Validator{
    private JValidator jValidator;

    public DubboBizValidator(URL url) {
        jValidator = new JValidator(url);
    }
    @Override
    public void validate(String methodName, Class[] parameterTypes, Object[] arguments) throws Exception {
        try {
            jValidator.validate(methodName, parameterTypes, arguments);
        }
        catch (ConstraintViolationException e){
            Set> violations = e.getConstraintViolations();
            RpcBizException bizException = new RpcBizException();
            if( null != violations && !violations.isEmpty() ) {
                ConstraintViolation violation = violations.iterator().next();
                bizException.setCode(ResponseCode.PARAM_VALID_ERROR.getCode());
                bizException.setMessage(violation.getMessage());
                log.info("ConstraintViolationException occured : " + violation.getMessage());
            }
            else {
                bizException.setCode(ResponseCode.INTERNAL_ERROR.getCode());
                log.error("ConstraintViolationException occured but no message returned.", e);
            }
            throw bizException;
        }
        
    }
}

要点在于将ConstraintViolationException异常捕获,封装为自定义的RpcBizException,用于后续过滤器处理。

4. 自定义Filter

参考文档:Dubbo调用拦截扩展

实现Dubbo的Filter接口,用于过滤RPC请求,并将自定义Validation抛出的异常封装为返回值返回给消费者。

import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
import static org.apache.dubbo.common.constants.FilterConstants.VALIDATION_KEY;

import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.validation.Validation;
import org.apache.dubbo.validation.Validator;

import xxx.CommonResponse;
import xxx.RpcBizException;

@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 9999)
public class DubboBizValidationFilter 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 validation 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 (RpcBizException e) {
                // 处理DubboBizValidator返回的异常
                Object retValue = CommonResponse.fail(e.getCode(), e.getMessage());
                return AsyncRpcResult.newDefaultAsyncResult(retValue, invocation);
            } catch (RpcException e) {
                throw e;
            } catch (Throwable t) {
                return AsyncRpcResult.newDefaultAsyncResult(t, invocation);
            }
        }
        return invoker.invoke(invocation);
    }

}

5. 配置

5.1 filter配置

在源码目录下创建文件:/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter

文件内容:

1)key:自定义filter名

2)value:自定义filter类全路径

dubboBizValidationFilter=xxx.DubboBizValidationFilter

5.2 validation配置

在源码目录下创建文件:/resources/META-INF/dubbo/org.apache.dubbo.validation.Validation

文件内容:

1)key:自定义validation名

2)value:自定义validation类全路径

dubboBizValidation=xxx.DubboBizValidation

5.3 dubbo配置

指定dubbo的validation和filter属性



 

其中"-validation"表示禁用Dubbo默认的filter,防止因优先级问题导致自定义filter无法执行。

建议在消费者和生产者两端都进行配置。消费者端配置参数校验可减少发生参数校验异常时的网络传输开销。

6. 使用参数校验框架

1)API定义

CommonResponse myMethod(@Valid XxxDTO dto)

返回值采用上文中的CommonResponse进行包装,确保异常消息序列化、反序列化能正确处理。

参数和SpringMVC中的用法一样,用javax.validation中的各种注解标注。

2)消费端处理示例

        CommonResponse ret = xxxService.myMethod(dto);
        log.info("ret code : " + ret.getCode());
        log.info("ret msg : " + ret.getMessage());

 

你可能感兴趣的:(微服务)