dubbo rpc调用参数校验

使用spring的时候http调用参数校验还是很方便的,只是我们rpc用的比较多,然后就有了这个了。

其实实现很简单,实现一个dubbo的Filter 然后在这里根据反射获取参数的注解使用javax.validation的注解做各种操作就行了。要使用的项目需要加入自定义的Filter
类似:


image.png

加上paramValidationFilter 多个Filter用英文逗号分割。
加这个的意思是,这个项目的全局rpc调用Filter加入自定义的参数校验。 就算你的项目不是使用xml的配置方法,也可有其他类似的行为。需要注意多个Filter的优先级

我们的rpc都是返回一个固定的包装对象 ResultData<T> 所以会返回一个有错误状态码和错误信息的包装ResultData给调用方
如果返回类型不为ResultData会抛出我们自定义的异常

上实现代码:

package com.jx.common.dubbo.filter;

import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.google.common.collect.Maps;
import com.jx.common.exception.BusinessException;
import com.jx.common.model.ResultData;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.*;
import javax.validation.groups.Default;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author 周广
 **/
@Slf4j
@Activate(order = -100)
public class ParamValidationFilter implements Filter {
    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();


    /**
     * 对象实体校验
     *
     * @param obj 待校验对象
     * @param <T> 待校验对象的泛型
     * @return 校验结果
     */
    private static <T> ValidationResult validateEntity(T obj) {
        Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
        return getValidationResult(set);
    }

    /**
     * 将校验结果转换返回对象
     *
     * @param set 错误信息set
     * @param <T> 校验对象的泛型
     * @return 校验结果
     */
    private static <T> ValidationResult getValidationResult(Set<ConstraintViolation<T>> set) {
        ValidationResult result = new ValidationResult();
        if (set != null && !set.isEmpty()) {
            result.setHasErrors(true);
            Map<String, String> errorMsg = Maps.newHashMap();
            for (ConstraintViolation<T> violation : set) {
                errorMsg.put(violation.getPropertyPath().toString(), violation.getMessage());
            }
            result.setErrorMsg(errorMsg);
        }
        return result;
    }

    /**
     * 方法级别的参数验证 手撕o(╥﹏╥)o
     *
     * @param annotation 待验证的注解
     * @param param      待校验参数
     * @param paramName  参数名称
     * @return 校验结果
     */
    static ValidationResult validateMethod(Annotation annotation, Object param, String paramName) {
        ValidationResult result = new ValidationResult();
        Map<String, String> errorMsg = Maps.newHashMap();
        result.setErrorMsg(errorMsg);

        if (annotation instanceof DecimalMax) {

            if (param instanceof BigDecimal) {
                BigDecimal value = new BigDecimal(((DecimalMax) annotation).value());
                if (((BigDecimal) param).compareTo(value) > 0) {
                    result.setHasErrors(true);
                    if ("{javax.validation.constraints.DecimalMax.message}".equals(((DecimalMax) annotation).message())) {
                        errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "大于" + value);
                    } else {
                        errorMsg.put(paramName, ((DecimalMax) annotation).message());
                    }
                }
            } else if (param instanceof BigInteger) {
                BigInteger value = new BigInteger(((DecimalMax) annotation).value());
                if (((BigInteger) param).compareTo(value) > 0) {
                    result.setHasErrors(true);
                    if ("{javax.validation.constraints.DecimalMax.message}".equals(((DecimalMax) annotation).message())) {
                        errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "大于" + value);
                    } else {
                        errorMsg.put(paramName, ((DecimalMax) annotation).message());
                    }
                }
            } else {
                result.setHasErrors(true);
                errorMsg.put(paramName, "类型:" + param.getClass().getName() + "与注解:" + annotation.getClass().getName() + "不匹配");
            }

        } else if (annotation instanceof DecimalMin) {
            if (param instanceof BigDecimal) {
                BigDecimal value = new BigDecimal(((DecimalMin) annotation).value());
                if (((BigDecimal) param).compareTo(value) < 0) {
                    result.setHasErrors(true);
                    if ("{javax.validation.constraints.DecimalMin.message}".equals(((DecimalMin) annotation).message())) {
                        errorMsg.put(paramName, paramName + param.getClass().getName() + " 值为:" + param + "小于" + value);
                    } else {
                        errorMsg.put(paramName, ((DecimalMin) annotation).message());
                    }
                }
            } else if (param instanceof BigInteger) {
                BigInteger value = new BigInteger(((DecimalMin) annotation).value());
                if (((BigInteger) param).compareTo(value) < 0) {
                    result.setHasErrors(true);
                    if ("{javax.validation.constraints.DecimalMin.message}".equals(((DecimalMin) annotation).message())) {
                        errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "小于" + value);
                    } else {
                        errorMsg.put(paramName, ((DecimalMin) annotation).message());
                    }
                }
            } else {
                result.setHasErrors(true);
                errorMsg.put(paramName, "类型:" + param.getClass().getName() + "与注解:" + annotation.getClass().getName() + "不匹配");
            }
        } else if (annotation instanceof Max) {
            long value = ((Max) annotation).value();
            if (Long.valueOf(param.toString()) > value) {
                result.setHasErrors(true);
                if ("{javax.validation.constraints.Max.message}".equals(((Max) annotation).message())) {
                    errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "大于" + value);
                } else {
                    errorMsg.put(paramName, ((Max) annotation).message());
                }
            }
        } else if (annotation instanceof Min) {
            long value = ((Min) annotation).value();
            if (Long.valueOf(param.toString()) < value) {
                result.setHasErrors(true);
                if ("{javax.validation.constraints.Min.message}".equals(((Min) annotation).message())) {
                    errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "小于" + value);
                } else {
                    errorMsg.put(paramName, ((Min) annotation).message());
                }
            }

        } else if (annotation instanceof NotNull) {
            if (param == null) {
                result.setHasErrors(true);
                if ("{javax.validation.constraints.NotNull.message}".equals(((NotNull) annotation).message())) {
                    errorMsg.put(paramName, "值为null");
                } else {
                    errorMsg.put(paramName, ((NotNull) annotation).message());
                }
            }
        } else if (annotation instanceof Size) {
            int value = Integer.valueOf(param.toString());
            if (value > ((Size) annotation).max()) {
                result.setHasErrors(true);
                if ("{javax.validation.constraints.Size.message}".equals(((Size) annotation).message())) {
                    errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "大于" + ((Size) annotation).max());
                } else {
                    errorMsg.put(paramName, ((Size) annotation).message());
                }
            } else if (value < ((Size) annotation).min()) {
                result.setHasErrors(true);
                if ("{javax.validation.constraints.Size.message}".equals(((Size) annotation).message())) {
                    errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "小于" + ((Size) annotation).min());
                } else {
                    errorMsg.put(paramName, ((Size) annotation).message());
                }
                errorMsg.put(paramName, param.getClass().getName() + " 值为:" + param + "小于" + ((Size) annotation).min());
            }
        }
        return result;
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Method method = null;

        for (Method m : invoker.getInterface().getDeclaredMethods()) {
            if (m.getName().equals(invocation.getMethodName()) && invocation.getArguments().length == m.getParameterCount()) {
                Class[] invokerMethodParamClassList = invocation.getParameterTypes();
                Class[] matchMethodParamClassList = m.getParameterTypes();
                if (verifyClassMatch(invokerMethodParamClassList, matchMethodParamClassList)) {
                    method = m;
                    break;
                }
            }
        }

        //如果找不到对应的方法就跳过参数校验
        if (method == null) {
            return invoker.invoke(invocation);
        }


        //一个参数可以有多个注解
        Annotation[][] paramAnnotation = method.getParameterAnnotations();
        //参数的class
        Class<?>[] paramClass = invocation.getParameterTypes();
        Object[] paramList = invocation.getArguments();
        //获取参数名称
        List<String> paramNameList = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.toList());

        for (int i = 0; i < paramList.length; i++) {
            Object param = paramList[i];


            Annotation[] annotations = paramAnnotation[i];
            if (annotations.length == 0) {
                continue;
            }


            //循环注解处理,快速失败
            for (Annotation annotation : annotations) {
                if (isJavaxValidationAnnotation(annotation)) {
                    ValidationResult result;
                    try {
                        result = validateMethod(annotation, param, paramNameList.get(i));
                    } catch (Exception e) {
                        log.error("参数校验异常", e);
                        return invoker.invoke(invocation);
                    }
                    if (result.isHasErrors()) {
                        if (ResultData.class.equals(method.getReturnType())) {
                            return generateFailResult(result.getErrorMsg().toString());
                        } else {
                            throw new BusinessException(result.getErrorMsg().toString());
                        }
                    }
                } else if (annotation instanceof Valid) {
                    if (param == null) {
                        String errMsg = String.format("待校验对象:%s 不可为null", paramClass[i].getName());
                        if (ResultData.class.equals(method.getReturnType())) {
                            return generateFailResult(errMsg);
                        } else {
                            throw new BusinessException(errMsg);
                        }
                    }
                    ValidationResult result = validateEntity(param);
                    if (result.isHasErrors()) {
                        if (ResultData.class.equals(method.getReturnType())) {
                            return generateFailResult(result.getErrorMsg().toString());
                        } else {
                            throw new BusinessException(result.getErrorMsg().toString());
                        }
                    }
                }
            }
        }

        return invoker.invoke(invocation);
    }

    private boolean verifyClassMatch(Class[] invokerMethodParamClassList, Class[] matchMethodParamClassList) {
        for (int i = 0; i < invokerMethodParamClassList.length; i++) {
            if (!invokerMethodParamClassList[i].equals(matchMethodParamClassList[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 构建失败返回对象
     *
     * @param errorMsg 错误信息
     * @return dubbo 返回对象
     */
    private Result generateFailResult(String errorMsg) {
        //请求参数非法
        return new RpcResult(new ResultData<>("0001", errorMsg));
    }

    /**
     * 判断是否为javax.validation.constraints的注解
     *
     * @param annotation 目标注解
     */
    private boolean isJavaxValidationAnnotation(Annotation annotation) {
        if (annotation instanceof AssertFalse) {
            return true;
        } else if (annotation instanceof AssertTrue) {
            return true;
        } else if (annotation instanceof DecimalMax) {
            return true;
        } else if (annotation instanceof DecimalMin) {
            return true;
        } else if (annotation instanceof Digits) {
            return true;
        } else if (annotation instanceof Future) {
            return true;
        } else if (annotation instanceof Max) {
            return true;
        } else if (annotation instanceof Min) {
            return true;
        } else if (annotation instanceof NotNull) {
            return true;
        } else if (annotation instanceof Null) {
            return true;
        } else if (annotation instanceof Past) {
            return true;
        } else if (annotation instanceof Pattern) {
            return true;
        } else if (annotation instanceof Size) {
            return true;
        }
        return false;
    }

    @Data
    static class ValidationResult {

        /**
         * 校验结果是否有错
         */
        private boolean hasErrors;

        /**
         * 校验错误信息
         */
        private Map<String, String> errorMsg;
    }
}

使用文档直接就从公司内部我写的wiki文档复制过来的, 送佛送到西
使用方法很简单,如果需要使用参数校验需要在接口的方法上面加上一些注解,注意是接口! 实现类加了没用


image.png

这里我们要区分2类参数

第一类:自定义对象 必须使用@Valid注解,标记这个对象需要进行校验
第二类:基本类型、包装类型、Decimal类型、String 等等一般都是java自带的类型

第二类可以使用:
第一个:javax.validation.constraints.DecimalMax
用于比较BigDecimal或者BigInteger
例子:

public void test1(@DecimalMax(value = "10", message = "最大为十") BigDecimal value)

意思是这个值最大为10 message是超出范围的时候的错误信息

第二个:

javax.validation.constraints.DecimalMin

和@DecimalMax 一样的使用方法,只是这个是最低 刚好相反

第三第四个:

javax.validation.constraints.Max

javax.validation.constraints.Min

例子:

public void test3(@Max(value = 10) int value) {
public void test4(@Min(value = 10,message = "最小为10") Integer value) 

这个可以用于比较多种整形数值类型

第五个:

javax.validation.constraints.NotNull

例子:

public void test5(@NotNull int value)

意思是校验的参数不可为null

第六个:

javax.validation.constraints.Size

例子:

public void test6(@Size(min = 10, max = 100,message = "最小10 最大100 不在10到100就报错") int value) 

设置范围的。

如上所述,虽然使用的是javax.validation.constraints下的注解,但是基本只支持注解的 value 和message 2个通用参数 别的都不支持! 因为我是手撕的校验 (validateMethod方法 逻辑可以自己调整)

接下来是第一类:自定义对象的校验方法


image.png

对数组、List使用的@NotEmpty 是org.hibernate.validator.constraints.NotEmpty 用于校验数组、List不为空

而且对象字段testClass2头上有2个注解

@NotNull
@Valid
NotNull用于标记这个对象自身不可为null, Valid用于标记这个对象内部还需要校验

所以testClass2List的2个注解也很容易理解了,就是标记List不可为null,且还要检查List里面每一个对象的字段

image.png

如图所示,如果是基本类型、包装类型、String、Decimal 等内建类型 可以使用如下注解

而且是完整的功能,除了上面说的value 和message2个通用参数,还可以使用其他所有javax.validation.constraints支持的功能。因为自定义对象的校验是使用javax.validation提供的校验

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容