• 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官方文档
      
  • 相关阅读:
    正则
    在开发过程中调试报表插件详细教程
    在开发过程中调试报表插件详细教程
    页面导出Excel文件总结
    java.lang.IllegalArgumentException: sheetName '' is invalid
    java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.util.Map
    表达式中的一些常用模式.
    C++使用libcurl做HttpClient
    C++ curl跨平台HttpClient
    java.lang.NumberFormatException: empty String
  • 原文地址:https://www.cnblogs.com/mingorun/p/11013095.html
Copyright © 2020-2023  润新知