Dubbo RPC調用參數校驗---錯誤message自動返回


Dubbo 的RPC調用中Consumer 和 Provider端都可以對調用的方法做傳參驗證,參數的驗證可以通過JSR303規范 (Java Specification Requests) 提到的 Bean Validation 方式來驗證, Dubbo官方也是這么推薦的。 最佳實踐中分包部分提到傳參的數據模型定義在API的jar包中,如果你是這樣做的,那么參數的驗證完全可以在Consumer端完成,這樣一來就可以減少網絡開銷並提早得到失敗結果。
    
下面的介紹基於 Dubbo2.6.2  + Springboot2.1.3 的環境,內容會在Dubbo官方文檔的基礎上做一些擴展。
 
Hibernate Validation 是Bean Validation 的一個實現,也是Dubbo官方推薦的實現方式,使用Hibernate Validation需要引入如下依賴:
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.16.Final</version>
</dependency>

<!-- 若啟動報錯 java.lang.ExceptionInInitializerError 則還需引入如下 el-api 依賴 -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

在傳參DTO中加上 "@NotNull" 注解

@Data
public class AddressDto implements Serializable {

    @NotNull(message = "地址不能為空")
    private String address;
    private String city;
    private Boolean batch;

}

 

傳參數據模型中添加需要驗證的注解后, consumer端在RPC調用前會經過一個叫做 ValidationFilter 的過濾器,該過濾期獲取到validator后會調用Dubbo提供的 JValidator  如下validate方法

@Override
public void validate(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
    List<Class<?>> groups = new ArrayList<Class<?>>();
    String methodClassName = clazz.getName() + "$" + toUpperMethoName(methodName);
    Class<?> methodClass = null;
    try {
        methodClass = Class.forName(methodClassName, false, Thread.currentThread().getContextClassLoader());
        groups.add(methodClass);
    } catch (ClassNotFoundException e) {
    }
    Set<ConstraintViolation<?>> violations = new HashSet<ConstraintViolation<?>>();
    Method method = clazz.getMethod(methodName, parameterTypes);
    Class<?>[] methodClasses = null;
    if (method.isAnnotationPresent(MethodValidated.class)){
        methodClasses = method.getAnnotation(MethodValidated.class).value();
        groups.addAll(Arrays.asList(methodClasses));
    }
    // add into default group
    groups.add(0, Default.class);
    groups.add(1, clazz);

    // convert list to array
    Class<?>[] classgroups = groups.toArray(new Class[0]);

    Object parameterBean = getMethodParameterBean(clazz, method, arguments);
    if (parameterBean != null) {
        violations.addAll(validator.validate(parameterBean, classgroups ));
    }

    for (Object arg : arguments) {
        validate(violations, arg, classgroups);
    }

    if (!violations.isEmpty()) {
        logger.error("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations);
        throw new ConstraintViolationException("Failed to validate service: " + clazz.getName() + ", method: " + methodName + ", cause: " + violations, violations);
    }
}
  
然后在for循環的中調用了另一 validate的私有方法,該方法對傳參DTO的類型做了判斷后 使用了Hibernate Validation 提供的 ValidatorImpl.validate 方法來完成的參數驗證,驗證完之后返回 ConstraintVoilation 的集合,在以上方法最后對集合violations做判斷是否非空,非空則代表驗證失敗, 並在后續拋出 ConstraintViolationException 異常,這個異常在經過你controller層之后,是可以被我們定義的 ControllerAdvice 捕獲到。全局異常處理方式 參考,這樣只需要增加一個 ExceptionHandler 處理ConstraintViolationException 就能將 Validation 中用到的注解的message返回調用端,
@ResponseBody
@ExceptionHandler(value = ValidationException.class)
public Result handleAddressException(ValidationException e){
    log.error(e.getMessage());
    String validateMsg = null;
    // 若判斷是 ConstraintViolationException異常 則取出message信息 
    if(e instanceof ConstraintViolationException){
        ConstraintViolationException cve = (ConstraintViolationException)e;
        Set<ConstraintViolation<?>> constraintViolations = cve.getConstraintViolations();
        for(ConstraintViolation cv : constraintViolations){
            validateMsg = cv.getMessage();
            break;
        }

        return ResultUtil.error(ResultEnum.VALIDATION_FAILURE.getCode(), validateMsg);
    }
    return ResultUtil.error(ResultEnum.VALIDATION_FAILURE.getCode(),e.getMessage());
}

 

最終在調用端獲取到JSON結果如下

{
    "requestId": null,
    "success": false,
    "business": null,
    "code": "20001",
    "message": "地址不能為空",
    "date": null,
    "version": null,
    "obj": null
}

 

參考:

[1] Dubbo官方文檔

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM