自己实现dubbo参数校验(类似RestFul 参数校验)

1. 场景:

因为工作中经常需要做参数校验,在springboot项目中使用@Valid+@NotNull、@NotBlank…注解开发API接口非常丝滑,相反在开发RPC接口时却还是需要编写大量的参数判断,严重影响主业务流程的开发(公司目前用的是Dubbo2.7.2)且代码整洁度、风格都受到了挑战。基于以上原因萌生了写一个PRC接口的验证,当然新版的dubbo已经支持了调用参数校验。

2. 原理:

因为我们要在consumer调用provider的过程中实现参数校验,而这个逻辑可以做为一个逻辑层,是否联想到AOP?而dubbo提供的filter机制刚好符合我们的要求。校验逻辑可以使用目前主流的校验框架hibernate-validator(省时省力又完善)。

3. 自定义实现:

DubboServiceParameterFilter类是我定义的dubbo filer类,当然作为filter需要使用dubbo的spi机制,在

/META-INF/dubbo/com.alibaba.rpc.Filter文件中添加:

dubboServiceParameterFilter=com.xxxx.filter.DubboServiceParameterFilter

Filter代码:

package com.example.api.demo.config.filter;

import com.google.common.collect.Maps;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.executable.ExecutableValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.*;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Slf4j
@Activate(group = {Constants.PROVIDER}, order = 1)
public class DubboServiceParameterFilter implements Filter {

    // 这段代码创建了一个静态的 ExecutableValidator 实例,用于执行验证操作。它通过 Validation 工具类的 buildDefaultValidatorFactory 方法
    // 获取默认的验证器工厂,并从中获取验证器实例。然后,使用 forExecutables 方法为执行体(executable)创建验证器。
    private static ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();

    private static final String VALIDATOR_MESSAGE = "参数验证失败";

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) {

        log.info("===================== param filter ========================");
        Method method = getMethod(invoker, invocation);
        if (Objects.nonNull(method)) {
            Class inf = invoker.getInterface();
            Object object = ServiceBean.getSpringContext().getBean(inf);
            Object[] paramList = invocation.getArguments();
            Set<ConstraintViolation<Object>> constraintViolations = executableValidator.validateParameters(object, method, paramList);
            Response response = getValidationResult(constraintViolations);
            if (Objects.nonNull(response.getData())) {
                return new RpcResult(response);
            }
        }
        return invoker.invoke(invocation);
    }

    /**
     * 获取校验方法
     */
    private static Method getMethod(Invoker<?> invoker, Invocation invocation) {

        Method[] methods = invoker.getInterface().getDeclaredMethods();
        for (Method m : methods) {
            boolean needCheck = m.getName().equals(invocation.getMethodName()) && invocation.getArguments().length == m.getParameterCount();
            if (needCheck) {
                if (matchMethod(invocation.getParameterTypes(), m.getParameterTypes())) {
                    return m;
                }
            }
        }
        return null;
    }

    //获取匹配的方法
    private static boolean matchMethod(Class[] invokerMethodParamClassList, Class[] matchMethodParamClassList) {
        for (int i = 0; i < invokerMethodParamClassList.length; i++) {
            if (!invokerMethodParamClassList[i].equals(matchMethodParamClassList[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 校验结果转换返回对象
     */
    private static <T> Response getValidationResult(Set<ConstraintViolation<T>> set) {
        if (set != null && !set.isEmpty()) {
            Map<String, String> errorMsg = Maps.newHashMap();
            for (ConstraintViolation<T> violation : set) {
                errorMsg.put(violation.getPropertyPath().toString(), violation.getMessage());
            }
            return Response.createError(VALIDATOR_MESSAGE, errorMsg);
        }
        return Response.createError(VALIDATOR_MESSAGE);
    }
}

 

posted @ 2024-05-30 14:48  威兰达  阅读(40)  评论(0编辑  收藏  举报