一个轻量级的参数校验框架

5,108 阅读17分钟
一、引言
      大家还在为冗余而又繁琐的方法或者对象属性的基础性边界校验、个性化的边界校验烦恼吗?下面介绍的这个小而美的框架【基础&可定制化的参数校验框架】能彻底解决大家的烦恼。作为一名经常写业务代码的码农,我们都遇到过类似的事情,每个业务方法或者RPC服务的参数是否为null、字符串是否为empty、数字是否大于或小于设定的阀值或是否在某个特定的区间、集合列表是否为空、以及一些特定领域的约定校验等; 想想以上的这些情形,我们可能在每个接口每个方法都需要写重复的校验判断代码,冗余的代码与业务逻辑耦合在一起,有没有一种优雅的解决办法,彻底分离这部分校验代码,做到校验逻辑与业务逻辑彻底解耦呢?答案就在下面。


二、框架原理
      在介绍框架的原理前,先说明一下,我们为什么要设计这么一个框架: 在电商体系下的业务系统目前大家基本都是采用中心化的RPC服务输出业务能力,比如:商品这个业务,所有电商里涉及到商品的业务能力基本由这个业务领域管理,最基本的有:商品查询【单个/批量】、商品发布/编辑、商品上下架、商品SKU管理、商品类目管理、商品库存管理等,在做这些业务服务时就遇到大量的需要对服务接口参数的合法性、有效性、约定性的校验工作,于是就有了这个动力,寻求一种更高抽象的解决办法。

  1. 框架内部运行机制      

   2. 框架角色介绍 

   1)校验规则:是框架里最底层的校验逻辑执行单元,基础校验、边界校验、个性化校验都在这个模块里完成,
              校验规则有基础的校验规则(比如为空,为NULL等),也有个性化的校验规则(比如针对不同
              业务的特殊校验规则)大家只要实现一个校验规则的接口,就可以写自己的校验规则。
             【稍后有代码实例】
   2)校验入口:并不是所有的接口服务需要做校验,校验入口就是告诉框架那个服务接口或方法需要做参数数据
              校验。
   3)校验支架:连接校验入口与校验规则。告诉框架在那个入口的某个参数上要执行哪个校验规则。他们三者的
              关系就比如烹饪一道菜,食材、菜谱、厨师之间的关系; 食材就好比校验规则,菜谱就好比是
              校验入口,厨师就是校验支架他能按照菜谱,选择适合的食材烹饪出一道美味的佳肴!
   他们之间的关系如下:

      

 3. 实现原理刨析

     1)我们先看一下校验规则的实现原理,代码如下:

         所有校验规则都需要实现 ParamCheck 这个接口,定义如下: 

/**
 * 校验规则的定义
 * 主要有两类:
 * 1、基本数据类型的为空为NULL等基本校验,这些我已经写好
 * 2、个性化自定义的校验,比如批量查询阀值校验,这些往往有特定的场景和背景,只需要实现该接口
 */
public interface ParamCheck {

    /**
     * 所有需要校验的逻辑类,都需要实现这个方法
     * @param t  待校验的值
     * @param objectType 待校验值的数据类型
     * @param c 自定义校验时的规则内容
     * @return CheckResult 校验结果&描述
     */
    CheckResult check(Object t, Class<?> objectType, String c) throws ServiceCheckException;

    /**
     * 检验规则的名称,通过这个名称来动态找到注解里配置的校验规则类
     * @return
     */
    String name();
}

这个接口定义了两个方法:
check()方法就是所有校验规则的基本校验逻辑.
name()方法返回校验规则的名称,系统会初始化加载这些规则并置入本地内存里,之后所有的校验工作都会由这些
在内存中的校验规则类来完成。


我们来分别刨析实现一个基础的校验规则类和一个个性化的校验规则类,如下:

/**
 * 基础校验规则-对象是否为NUll的校验,最基础的校验,每一个参数都需要检验这一步
 */
@CheckRule
public class ObjectCheck implements ParamCheck {
    /**
     * 所有需要校验的逻辑类,都需要实现这个方法
     *
     * @param t          待校验的值
     * @param objectType 待校验值的数据类型
     * @param c          自定义校验时的规则内容
     * @return CheckResult 校验结果&描述
     */
    @Override
    public CheckResult check(Object t, Class<?> objectType, String c) {
        return CheckUtils.objectIsNullCheck(t);
    }

    /**
     * 检验规则的名称,通过这个名称来动态找到注解里配置的校验规则类
     *
     * @return
     */
    @Override
    public String name() {
        return Object.class.getName();
    }
}

/**
 * 个性化校验规则-列表的个数是否超越设定的阀值
 */
@CheckRule
public class MaxSizeCheck implements ParamCheck {

    private final static Logger logger = LoggerFactory.getLogger(MaxSizeCheck.class);

    /**
     * 所有需要校验的逻辑类,都需要实现这个方法
     *
     * @param t          待校验的值
     * @param objectType 待校验值的数据类型
     * @param c          自定义校验时的规则内容
     * @return CheckResult 校验结果&描述
     */
    @Override
    public CheckResult check(Object t, Class<?> objectType, String c) throws ServiceCheckException {

        CheckResult checkResult = new CheckResult(true);

        //如果校验列表的个数是否超越设定的阀值,没有传阀值过来,默认通过
        if(StringUtils.isEmpty(c)){
            return checkResult;
        }

        Integer maxSize;
        try {
            /**
              * 这里可以做优化,将所有个性化的校验条件加载时都初始化好
              */
            JSONObject objectCondition = JSON.parseObject(c);
            maxSize = objectCondition.getInteger("maxSize");

            if (null == maxSize) {
                return checkResult;
            }
        }catch (Exception e){
            logger.error("MaxSizeCheck Error: msg="+c,e);
            throw new ServiceCheckException("MaxSizeCheck Error: msg=" + c + e.getMessage());
        }

        return CheckUtils.sizeGreaterThanFeedCheck(t,maxSize,objectType);
    }

    /**
     * 检验规则的名称,通过这个名称来动态找到注解里配置的校验规则类
     *
     * @return
     */
    @Override
    public String name() {
        return this.getClass().getSimpleName();
    }
}

 注意:每一个规则都需要加一个注解,系统就是通过这个注解来识别校验规则,并自动加载到内存里的。 

校验规则注解代码如下:

/**
 * 校验规则注解,每一个校验规则类加一个这个注解,就可以通过
 * applicationContext.getBeansWithAnnotation() 完成初始化
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface CheckRule {
    String name() default ""; //校验规则名称(确保唯一)
}

以上代码,就是校验规则的核心代码及运行机制,那么校验规则可以横向的无限扩展,您只需要实现 ParamCheck 这个接口,并在规则前面使用@CheckRule 标注一下即可,系统就会自动把您的校验规则加载到内存里。那么校验规则有了,该如何使用呢?看如下的代码:

public class TagWriteServiceImpl implements TagWriteService {

    @Autowired
    private TagManager tagManager;

    /**
     * @param tagOption
     * @return
     * @ParameterCheck 这个注解是 接口的入参校验 通过注解+AOP完成
     */
    @ParameterCheck(exceptionHandler = ServiceCheckFailedHanlder.class)
    public ResultDO<Boolean> tagAdd(tagWriteOption tagOption) {
        
        //todo 自己的业务逻辑
        (在做业务逻辑之前,系统其实已经通过ParameterCheck这个注解对 tagWriteOption这个对象&对象
        指定的属性做了基础的校验工作)
        
        .......    
    }
}

使用刨析:

第一步:需要在待进行参数校验的方法前面加 @ParameterCheck这个注解。
第二步:在@ParameterCheck注解里指定一个校验不通过的错误信息返回对象,如上代码中的 ServiceCheckFailedHanlder
      类。这个类的具体实现如下:
 
public class ServiceCheckFailedHanlder implements ICheckFailedHandler {    
    /**
      * 框架本身是一个通用的框架,但总会有一些信息是需要定制化的,比如不同的业务代码中有自己不同的错误提示包装类等
      * 框架为了增加通用性和灵活性,这里框架只定义了一个接口ICheckFailedHandler,
      * 具体的有业务方自己去实现      
      */
    @Override
    public Object validateFailed(String msg, Class returnType, Object... args) {
         
        //todo,这里就可以写自己业务的错误信息封装代码了
        BaseResultDO result = new BaseResultDO<>();
        result.setSuccess(false);
        result.setResultCode(BaseResultTypeEnum.PARAM_ERROR.getCode());
        result.setResultMessage(msg);
        return result;
    }
}

上面说到,在做业务逻辑之前,系统其实已经通过ParameterCheck这个注解对 tagWriteOption这个对象&对象
指定的属性做了基础的校验工作,是如何做到的呢?

1、首先只要方法上有ParameterCheck这个注解,系统都会针对方法里边所有的参数进行基本的为NULL校验。实现如下:
   系统通过AOP自动拦截有ParameterCheck注解的方法。AOP拦截实现如下:

@Component
@Aspect
@Order(1)
public class ParamCheckAop  extends AbsAopServiceParamterCheck {    

     private static Logger logger = LoggerFactory.getLogger("aopServiceParameterCheck");

    /**
     * 只要有ParameterCheck 这个注解的方法,都会被拦截
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("@annotation(parameterCheck)")
    public Object around(ProceedingJoinPoint pjp, ParameterCheck parameterCheck) throws Throwable {

        long startTime = System.currentTimeMillis();

        //执行校验方法
        CheckResult checkSuccess = super.check(pjp, true);
        long annExceTime = System.currentTimeMillis() - startTime;
        if (logger.isDebugEnabled()) {
            logger.debug(pjp.getTarget().getClass().getSimpleName() + "|checkTime=" + annExceTime);
        }


        if (!checkSuccess.isSuccess()) {
            Method method = getMethod(pjp);
            ICheckFailedHandler handler = CheckFailedHandlerWrapper.getInstance()
                    .getCheckFailedHander(parameterCheck.exceptionHandler());

            return handler.validateFailed(checkSuccess.getMsg(),
                    method.getReturnType(),
                    pjp.getArgs());
        }


        return pjp.proceed();
    }

    private Method getMethod(JoinPoint pjp) {
        MethodSignature method = (MethodSignature) pjp.getSignature();
        return method.getMethod();
    }
}
 

AbsAopServiceParamterCheck是框架提供的一个抽象类,实现如下:

public abstract class AbsAopServiceParamterCheck {    
    private static Logger logger = LoggerFactory.getLogger(AbsAopServiceParamterCheck.class);

    @Resource
    private ServiceParameterCheck serviceParameterCheck;

    protected CheckResult check(ProceedingJoinPoint pjp, boolean isWriteLog) throws Throwable {

        Signature sig = pjp.getSignature();
        MethodSignature msig;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = pjp.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

        //当前方法是否ParameterCheck这个注解
        if(currentMethod.isAnnotationPresent(ParameterCheck.class)){

            //方法参数
            Object[] args = pjp.getArgs();
            Object[] params = new Object[args.length+2];
            params[0] = pjp.getTarget();         //类名全路径
            params[1] = currentMethod.getName(); //方法名
            for(int i = 0;i<args.length;i++){
                params[i+2] = args[i];
            }

            //执行校验方法-参数的基本校验 + 自定义的校验
            CheckResult checkBaseParamResult = serviceParameterCheck.checkMethod(params);
            if(!checkBaseParamResult.isSuccess()){
                logger.warn(pjp.getTarget().getClass().getSimpleName()+"."+currentMethod.getName()+"|checkSuccess=false"+"|param="+ JSON.toJSONString(args));
                return checkBaseParamResult;
            }

            //执行校验方法-参数如果是自定义对象还需要校验一下 对象里的属性是否有校验规则

            CheckResult checkObjectParamResult = serviceParameterCheck.batchCheckObjecs(args);
            if(!checkObjectParamResult.isSuccess()){
                logger.warn(pjp.getTarget().getClass().getSimpleName()+"."+currentMethod.getName()+"|checkSuccess=false"+"|param="+JSON.toJSONString(args));
                return checkObjectParamResult;
            }

            if(isWriteLog){
                logger.warn("look i am here");
            }
        }
        return new CheckResult(true);
    }
}

基础的校验就是这一行代码:

CheckResult checkBaseParamResult = serviceParameterCheck.checkMethod(params);

代码如下:

@Service
public class ServiceParameterCheck implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    /**
     * 注册的初始化对象列表
     */
    private Map<String,IAnnotationManager> annotationManagerMap = new HashMap<>();

    /**
     * 在初始化类的时候执行,将每一个注册的对象的属性缓存起来
     */
    @PostConstruct
    protected void init() throws ServiceCheckException {

        Map<String,Object> objectMap = applicationContext.getBeansWithAnnotation(ServiceCheckPoint.class);

        /**  初始化-入参【对象级&方法级】的注解属性 **/
        for(Object o : objectMap.values()){
            if(o instanceof IAnnotationManager){
                annotationManagerMap.put(((IAnnotationManager)o).getAnnotationCheckType().name(),((IAnnotationManager)o));
                ((IAnnotationManager)o).init();
            }
        }
    }

    /**
     * 根据方法上的入参做校验
     * @param args
     * @return
     */
    public CheckResult checkMethod(Object ...args){
        return annotationManagerMap.get(AnnotationCheckType.METHOD.name()).check(args);
    }

    /**
     * 根据入参对象上的注解
     * @param o
     * @return
     */
    public CheckResult checkObject(Object o){
        return annotationManagerMap.get(AnnotationCheckType.OBJECT.name()).check(o);
    }

    /**
     * 根据入参对象上的注解批量校验
     * @param objects
     * @return
     */
    public CheckResult batchCheckObjecs(Object[] objects){

        IAnnotationManager iAnnotationManager = annotationManagerMap.get(AnnotationCheckType.OBJECT.name());

        if(ArrayUtils.isEmpty(objects)){
            return new CheckResult(true);
        }

        for(Object o : objects){
            Class<?> objectType = o.getClass();
            if(objectType.getSimpleName().endsWith("List")){
                o = ((List)o).get(0);
            }else if(objectType.getSimpleName().endsWith("Map")){
                o = ((Map)o).values().toArray()[0];
            }else if(objectType.getSimpleName().endsWith("Set")){
                o = ((Set)o).toArray()[0];
            }else if(objectType.isArray()){
                o = Arrays.asList(o).get(0);
            }


            CheckResult checkRult = iAnnotationManager.check(o);
            if(!checkRult.isSuccess()){
                return checkRult;
            }
        }

        return new CheckResult(true);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}  

具体校验在这里:

/**
 * 针对服务接口的方法参数做校验
 */
@ServiceCheckPoint
public class ServiceMethodAnnotationManager  implements IAnnotationManager,ApplicationContextAware {
    private final static Logger logger = LoggerFactory.getLogger(ServiceMethodAnnotationManager.class);

    /**
     * 每个服务对应的方法集合
     */
    private Map<String,Method> methodMap = new HashMap<>();

    /**
     * 每个服务对应的方法参数列表
     */
    private Map<String,List<Class<?>>> methodParamMap = new HashMap<>();

    /**
     * 前面两位用作其他用途
     */
    private final static Integer reserveLen = 2;

    @Resource
    ParamCheckManager paramCheckManager;

    private ApplicationContext applicationContext;

    @Override
    @PostConstruct
    public void init() throws ServiceCheckException {

        Map<String,Object> objectMap = applicationContext.getBeansWithAnnotation(ServiceMethodCheck.class);

        try {
            for(Object o: objectMap.values()){
                Class<?> clazz = o.getClass().getSuperclass();

                //获取方法列表,如果没有注册,直接跳出返回
                Method[] methods = clazz.getDeclaredMethods();
                if(ArrayUtils.isEmpty(methods)){
                    break;
                }

                for(Method method : methods){
                    //方法上是否有ParameterCheck这个注解
                    if(method.isAnnotationPresent(ParameterCheck.class)){
                        String key = clazz.getName() + "." + method.getName();

                        Class<?>[] parameterTypes = method.getParameterTypes();
                        if(!ArrayUtils.isEmpty(parameterTypes)) {
                            methodParamMap.put(key, Arrays.asList(parameterTypes));
                        }
                        methodMap.put(key,method);
                    }
                }
            }

            logger.warn(" ServiceMethodAnnotationManager init success ,methodMap:" + JSON.toJSONString(methodMap));

        }catch (Exception e){
            logger.error("ServiceMethodAnnotationManager error!",e);
            throw new ServiceCheckException("ServiceMethodAnnotationManager Init Error! " + e.getMessage());
        }
    }

    /**
     * 具体执行校验的入口之一
     * @param args
     * @return  CheckResult 校验结果 & 错误描述
     */
    @Override
    public CheckResult check(Object... args) {
        CheckResult checkResult = new CheckResult(true);

        /**参数列表为空,直接返回true,不做校验**/
        if(ArrayUtils.isEmpty(args)){
            return checkResult;
        }

        /**参数长度必须大于两个,第一个是接口服务类对象,第二个是调用的方法签名,剩余的是入参**/
        if(args.length < reserveLen){
            return checkResult;
        }

        Object[] objects = args;

        //第二个是调用的方法签名
        String methodName = args[1].toString();
        //类名+方法名作为key
        String key = args[0].getClass().getName()+"."+methodName;

        /** 这个类+方法下的参数列表,methodParamMap在初始化的时候已经设置好了 **/
        List<Class<?>> paramTypeNameList = methodParamMap.get(key);

        /** 说明不需要检验 **/
        if(CollectionUtils.isEmpty(paramTypeNameList)){
            return checkResult;
        }

        //获取对应key的方法对象
        Method method = methodMap.get(key);

        //获取对应方法上的注解
        ParameterCheck annotation = method.getAnnotation(ParameterCheck.class);
        if(null == annotation){
            return checkResult;
        }

        //获取方法参数里对应的注解列表
        Map<Integer,Annotation> annotationAndParamIndexMap = getAnnotationAndParamIndex(method);

        /**
         * 循环校验传入的参数,为什么要从2开始,
         * 因为第一个参数是服务对象。
         * 第二个参数是方法签名。
         * 从第三个参数开始,才是方法的参数列表
         */
        try {
            for (int i = reserveLen; i < objects.length; i++) {
                int paramIndex = i-reserveLen;

                //字段上有uncheck这个注解,说明不需要做检验,忽略
                if (isCheck(annotationAndParamIndexMap, paramIndex)) {
                    //如果参数上没有自定义注解,那么就以方法注解上的自定义注解为检验的规则
                    if(isHaveSelfCheck(annotationAndParamIndexMap, paramIndex)){
                        //参数上的自定义检验规则注解
                        SelfCheck paramAnnotation = (SelfCheck)annotationAndParamIndexMap.get(paramIndex);
                        checkResult = paramCheckManager.check(objects[i], paramTypeNameList.get(paramIndex),
                                Arrays.asList(paramAnnotation.check()),
                                paramAnnotation.condition(),
                                paramAnnotation.msg());
                    }else{
                        checkResult = paramCheckManager.check(objects[i], paramTypeNameList.get(paramIndex),
                                Arrays.asList(annotation.selfCheck()),
                                annotation.condition(),
                                annotation.msg());
                    }

                    if (!checkResult.isSuccess()) {
                        return checkResult;
                    }
                }
            }
        }catch (Exception e){
            /**如果检验里边发生了异常,默认通过校验。可以往下走*/
            logger.error("ServiceMethodAnnotationManager error ,msg=",e);
        }

        return new CheckResult(true);
    }

    /**
     * 每个实现这个接口,都需要返回一个校验的级别:方发入参级别
     * @return
     */
    @Override
    public AnnotationCheckType getAnnotationCheckType() {
        return AnnotationCheckType.METHOD;
    }

    /**
     * 获取不需要检查的参数的索引集合
     * @param method
     * @return
     */
    private Map<Integer,Annotation> getAnnotationAndParamIndex(Method method){
        Map<Integer,Annotation> annotationAndParamIndexMap = new HashMap<>();

        //获取方法参数里是否有 uncheck这个注解
        Annotation[][] annotations = method.getParameterAnnotations();
        if(!ArrayUtils.isEmpty(annotations)) {
            for (int i = 0; i < annotations.length; i++) {
                for (int j = 0; j < annotations[i].length; j++) {
                    //把参数及对应参数上的注解记录解析出来 循环下标是从0开始,i=0 实际上是指第一个参数。
                    if(null != annotations[i][j]) {
                        annotationAndParamIndexMap.put(i,annotations[i][j]);
                    }
                }
            }
        }

        return annotationAndParamIndexMap;
    }

    /**
     * 字段上有uncheck这个注解,不需要做检验,忽略
     * @param annotationAndParamIndexMap
     * @param paramIndex
     * @return
     */
    private boolean isCheck(Map<Integer,Annotation> annotationAndParamIndexMap,Integer paramIndex) {
        if(CollectionUtils.isEmpty(annotationAndParamIndexMap)){
            return true;
        }

        //如果对应的参数里包含 Uncheck这个注解,就返回false,不校验这个参数
        if(annotationAndParamIndexMap.get(paramIndex) instanceof Uncheck){
            return false;
        }

        return true;
    }

    /**
     * 字段上是否有 自定义的注解 selfCheck
     * @param annotationAndParamIndexMap
     * @param paramIndex
     * @return
     */
    private boolean isHaveSelfCheck(Map<Integer,Annotation> annotationAndParamIndexMap,Integer paramIndex){
        if(CollectionUtils.isEmpty(annotationAndParamIndexMap)){
            return false;
        }

        if(annotationAndParamIndexMap.get(paramIndex) instanceof SelfCheck){
            return true;
        }

        return false;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}          

这一行代码就是具体的校验
checkResult = paramCheckManager.check(objects[i], paramTypeNameList.get(paramIndex),
                                Arrays.asList(paramAnnotation.check()),
                                paramAnnotation.condition(),
                                paramAnnotation.msg());

我们看下paramCheckManager的实现如下:

@Service
public class ParamCheckManager {
    @Resource
    private ParamCheckCollection paramCheckCollection;

    /**
     * 基础校验&自定义校验
     * 基础校验是必须要做的,自定义校验根据注解上的配置来决定是否要做
     * @param v
     * @param objectType
     * @param selfChecks
     * @param condition
     * @param failMsg
     * @return
     */
    public CheckResult check(Object v,
                             Class<?> objectType,
                             List<String> selfChecks,
                             String condition,
                             String failMsg) throws ServiceCheckException {

        //基础的校验,对象为NULL,为EMPTY
        CheckResult baseCheck = paramCheckCollection.getParamCheckInstance(objectType.getName()).check(v, objectType, condition);

        //基础校验不通过,直接返回false
        if (!baseCheck.isSuccess()) {
            return baseCheck;
        }

        //加载自定义的数据校验
        if (!CollectionUtils.isEmpty(selfChecks)) {
            for (String selfCheck : selfChecks) {
                if (!StringUtils.isEmpty(selfCheck)) {
                    CheckResult checkRult = paramCheckCollection.getParamCheckInstance(selfCheck).check(v, objectType, condition);

                    //自定义校验不通过,直接返回false
                    if (!checkRult.isSuccess()) {
                        // 使用用户自定义的校验失败信息
                        if (StringUtils.isNotBlank(failMsg)) {
                            checkRult.setMsg(failMsg);
                        }
                        return checkRult;
                    }
                }
            }
        }

        return new CheckResult(true);
    }
}

其中 ParamCheckCollection这里封装了所有的校验规则(包括框架里的和将来自己要写的都是在这里加载完成的)

/**
 * 所有注册的检验类(对象类型的、集合类型的、自定义类型等)
 */
@Service
public class ParamCheckCollection  implements ApplicationContextAware {
    private Map<String,ParamCheck> paramCheckMap;
    private ApplicationContext applicationContext;

    /**
     * 初始化完成所有校验的规则类,并按照名称植入map中
     */
    @PostConstruct
    protected void init(){
        Map<String,Object> tempParamCheckMap = applicationContext.getBeansWithAnnotation(CheckRule.class);

        if(!CollectionUtils.isEmpty(tempParamCheckMap)){
            paramCheckMap = new HashMap<>(tempParamCheckMap.size());
            for(Object o : tempParamCheckMap.values()){
                if(o instanceof ParamCheck){
                    ParamCheck paramCheck = (ParamCheck)o;
                    paramCheckMap.put(paramCheck.name(),paramCheck);
                }
            }
        }
    }

    /**
     * 返回对应规则名称的校验类,如果没有找到对应的规则类,那么返回对象检验规则类
     * @param checkName
     * @return
     */
    public ParamCheck getParamCheckInstance(String checkName){
        if(StringUtils.isEmpty(checkName)){
            return paramCheckMap.get(Object.class.getName());
        }
        ParamCheck iParamCheck = paramCheckMap.get(checkName);
        return (null != iParamCheck)? iParamCheck : paramCheckMap.get(Object.class.getName());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

2、对象属性上的校验是如何做到的呢?
  待校验的对象代码如下:
  @MinNum 就是框架里的一个个性化校验规则:校验数字的大小是否大于某个数字,数字由业务自己定义。
  @ParameterCheck 是一个基础校验规则:校验对象是否为NULL。
  属性前没有注解的,默认不需要进行校验,框架就不会去校验这些属性。
  校验逻辑,如上面所讲,框架会加载类里的属性并,检查类的属性里是否有注解。

public class TagWriteOption implements Serializable {
    private static final long serialVersionUID = -1639997547043197452L;

    /**
     * 商品id,必填
     */
    @MinNum(value = 1, msg = "itemId需要大于0")
    private Long itemId;

    /**
     * 商品所属市场
     */
    @ParameterCheck(msg = "market不能为空")
    private Integer market;


    /**
     * 调用的业务系统的名称
     */
    private String appName;
}

3、方法上有ParameterCheck这个注解,系统都会针对方法里边所有的参数进行基本的为NULL校验,
   那如果有些方法的参数我不想做校验,如何实现?
   
   框架考虑到了这个情况,您只需要在该参数前面加@Uncheck注解,框架检测到参数前有这个注解,
   就会忽略这个参数的校验,上面的代码中有实现。

/**
 * 不需要检查
*/
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Uncheck {}

至此,您已经知道了这个框架的核心实现原理和代码,并知道如何定义自己的校验规则运用到您自己的业务场景里去。完全是透明可扩展的,框架其实就是定义了一个通用的规则执行与规则定义标准以及集成了基础的校验规则。大家完全可以在框架源码的基础上,进行二次开发与规则定义以达到更加个性化的业务场景校验。

目前框架支持的校验注解如下:

@MinNum 数字最小值校验

@MaxNum 数字最大值校验

@MinSize 集合最小个数校验

@MaxSize 结合最大个数校验

@StrSize 字符串长度校验

@NotNull 非空校验

@SelfDef 自定义校验

@Num 数字区间校验

@CollectionSize 集合区间校验


4. 框架特点总结

    1)代码隔离,将校验的代码逻辑与业务逻辑代码彻底隔离解耦。

        数据校验的代码,全部独立封装到一个个的校验规则里,大家可以维护一个自己的校验规则库,用的时候只需要配置一下注解+规则名称即可。


   2)可扩展性极强,校验规则可根据业务场景实际情况支持无限横向扩展。


   3)性能极佳,通过预加载模块将所有类加载到内存,使用时无需创建对象。


   4)代码0侵入完成,只需要配置几个注解,就可以实现基础&个性化的入参数据校验,对业务代码无任何污染。

   5)智能化校验,框架可根据参数的数据类型实现智能的数据校验,针对参数的类型做对应的基础校验,比如对象为null,字符串为空,列表大小等,自定义的对象,根据对象属性上的注解完成必要的校验。

   6)校验结果按需定制,不同的业务领域或代码对于入参数据校验不通过的返回有不一样的提示信息,框架提供灵活的校验结果返回信息,仅需要实现一个异常返回接口即可。

   7)校验规则配置灵活,默认所有参数都会做基础校验,也可以校验指定的参数,当然也可以指定不需要进行校验等。

   8)代码高度重复利用,对于一个基础性的校验,只需要封装在一个基础性的校验规则里即可,以后使用只需要配置一个注解,就可以重复利用,间接的提升了开发效率和代码维护成本。


本文主要介绍了框架的基础原理和如何使用,限于篇幅和个人能力原因,大家在阅读过程中有任何疑问和问题,可以直接提问,欢迎大家提出任何建议和意见,一起完善这个框架,让这个框架做的更好,更有价值和意义!

框架是我和公司另外一个同事一起完成,大家有问题可以发邮件给我们wangchun_166@126.com、yihuihuiyi@gmail.com,我们随时恭候您的建议和意见!