基于oval注解与自定义aspect应用对接口DTO数据约束校验

我们在对外提供接口服务时,对于参数校验总会存在大量的if条件语句判断,代码冗余,本来代码看起来挺干净整洁的,但是恰巧看到这些大量的if判断语句代码,显得那么碍眼。因此,我们在开发的道路上,总会想到是否有更好地方法以解决这种碍眼的代码?鉴于我的实践道路上,我悟出了自己的一条光明之路,以分享给大家,那就是基于oval的entity注解+aspect实现。

###1、初识Oval
oval,是一个java开源验证框架,功能非常强大,使用简单,它提供基于注解的形式应用在entity的字段上。
#####第一步:如果我们使用它,maven工程需要引入依赖

<dependency>
  <groupId>net.sf.oval</groupId>
  <artifactId>oval</artifactId>
  <version>1.90</version>
</dependency>

#####第二步:对实体对象添加field字段注解验证
示例如下

import com.lyqc.base.common.BaseDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.sf.oval.constraint.NotEmpty;
import net.sf.oval.constraint.NotNull;
import net.sf.oval.constraint.Size;

import java.util.List;

/**
 * @description: GPS安装信息提交DTO对象
 * @Date : 下午7:48 2018/1/3
 * @Author : 石冬冬-Heil Hitler
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GpsSubmitDTO extends BaseDTO {
    private static final long serialVersionUID = -6165001330649484325L;
    /**
     * 单据号
     */
    @NotEmpty(message = "申请单号不能为空")
    private String appCode;
    /**
     * gps安装附件集合
     */
    @NotNull(message = "GPS安装附件不能为空")
    @Size(message = "GPS安装附件不能为空")
    private List<AppAnnexDTO> annexList;
    /**
     * gps影像件集合
     */
    @NotNull(message = "gps影像件不能为空")
    @Size(message = "gps影像件不能为空")
    private List<AppAnnexImgDTO> annexImgList;
}

#####第三步:调用oval相应api

GpsSubmitDTO submitDTO = new GpsSubmitDTO();
Validator validator = new Validator();
List<ConstraintViolation> violations = validator.validate(submitDTO);
if (CollectionsTools.isNotEmpty(violations)) {
   System.out.println(violations.get(0).getMessage());
}

###2、自定义Oval注解约束
业务场景:比如我们实现一个字段必须某个枚举成员变量
#####第一步:自定义一个注解

import com.lyqc.base.enums.EnumValue;
import net.sf.oval.configuration.annotation.Constraint;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @description: 用户规约DTO相关必须枚举字段的注解约束
 * @Date : 2018/6/2 下午2:42
 * @Author : 石冬冬-Seig Heil
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Constraint(checkWith = EnumValidator.class)
public @interface EnumValidation {
    /**
     * 枚举调用方法
     */
    enum InvokeMethod{
        getIndex,getName
    }
    /**
     * 是否可以为空
     * @return
     */
    boolean acceptNull() default false;
    /**
     * 调用枚举方法
     * @return
     */
    InvokeMethod method() default InvokeMethod.getIndex;
    /**
     * 校验信息,支持形如 "[户口性质]不合法,必须({0})",这时会取对应枚举的成员替换消息模板中的占位符
     * @return
     */
    String message() default "非法枚举值";
    /**
     * 枚举类
     * @return
     */
    Class<?> enums() default EnumValue.class;
}

#####第二步:实现抽象类AbstractAnnotationCheck

import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import net.sf.oval.exception.OValException;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * @description: 用户规约DTO相关必须枚举字段的注解约束
 * @Date : 2018/6/2 下午3:00
 * @Author : 石冬冬-Seig Heil
 */
public class EnumValidator extends AbstractAnnotationCheck<EnumValidation> {
    @Override 
    public boolean isSatisfied(Object target, Object value, OValContext ctx, Validator validator) throws OValException {
        try {
            if(null == value || "".equals(value.toString())){
                return true;
            }
            EnumValidation constraint = ((FieldContext) ctx).getField().getDeclaredAnnotation(EnumValidation.class);
            Class<?> clazz = constraint.enums();
            if(clazz.isEnum() && !constraint.acceptNull() ){
                EnumValidation.InvokeMethod method = constraint.method();
                Method  getIndex = clazz.getMethod(method.name());
                List<String> indexes = new ArrayList<>();
                for(Object obj : clazz.getEnumConstants()){
                    indexes.add(getIndex.invoke(obj).toString());
                }
                String message = MessageFormat.format(constraint.message(),indexes.toString());
                super.setMessage(message);
                return indexes.contains(value.toString());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }
}

#####第三步:对entity类,应用这个自定义注解

@EnumValidation(enums = ConstEnum.SexEnum.class,message = "[性别]不合法,必须(0/1)")
@ApiModelProperty(name="sex",value="性别 0:男性,1:女性;",dataType="String")
@NotEmpty(message = "[性别]不能为空")
private String sex;

我们的ConstEnum.SexEnum.class源码

/**
     * @description: 性别 枚举
     * @Date : 2018/7/3 上午11:08
     * @Author : 石冬冬-Seig Heil
     */
    public enum SexEnum implements EnumValue {
        MALE(0,"男性"),
        FEMALE(1,"女性");
        SexEnum(int index, String name) {
            this.index = index;
            this.name = name;
        }

        private int index;
        private String name;

        @Override
        public int getIndex() {
            return this.index;
        }

        @Override
        public String getName() {
            return this.name;
        }

        /**
         * 根据索引获取名称
         * @param index 索引
         * @return
         */
        public static String getNameByIndex(int index){
            for(SexEnum e : SexEnum.values()){
                if(e.getIndex() == index){
                    return e.getName();
                }
            }
            return null;
        }

        /**
         * 根据索引获取枚举对象
         * @param index 索引
         * @return
         */
        public static SexEnum getByIndex(int index){
            for(SexEnum e : SexEnum.values()){
                if(e.getIndex() == index){
                    return e;
                }
            }
            return null;
        }

    }

EnumValue是一个接口,源码如下:

/**
 * @description: 枚举接口,一切枚举应从通用扩展宜实现此接口
 * @Date : 2017/9/19 下午6:22
 * @Author : 石冬冬-Seig Heil
 */
public interface EnumValue {

    /**
     * 获取枚举索引
     * @return
     */
    int getIndex();

    /**
     * 获取枚举名称
     * @return
     */
    String getName();
}

###3、定义aspect注解,并作用调用方法
业务场景:为了解决相应服务接口,内部有相关对于DTO约束校验(比如非空校验,长度校验等),这时,我们可以定义一个注解,然后定义一个aspect切面。
#####第一步:定义一个处理DTO校验的注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description 基于oval的注解的DTO约束校验注解
 * @Date : 2018/6/2 下午2:32
 * @Author : 石冬冬-Seig Heil
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OvalValidator {
    /**
     * 模块名称
     * 请注意,如果不定义请使用module值
     * @return
     */
    String value() default "未知模块";
    /**
     * 动作
     * @return
     */
    Action action() default @Action();
}

#####第二步:实现相应aop处理业务逻辑
说明:@annotation(com.mljr.annotation.OvalValidator)是处理相应地方法应用了这个注解,将会作为切入点处理。

import com.alibaba.fastjson.JSON;
import com.lyqc.base.common.Result;
import com.lyqc.base.enums.RemoteEnum;
import com.mljr.annotation.OvalValidator;
import com.mljr.util.CollectionsTools;
import com.mljr.util.StringTools;
import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.List;

/**
 * @description: 基于oval的注解的DTO约束校验
 * @Date : 2018/6/2 下午2:32
 * @Author : 石冬冬-Seig Heil
 */
@Aspect
@Component
public class OvalValidatorAdvice {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Pointcut("@annotation(com.mljr.annotation.OvalValidator)")
    public void validator() {
    }

    @Around("validator()")
    public Object intercept(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        OvalValidator annotation = method.getAnnotation(OvalValidator.class);
        if (annotation == null) {
            return joinPoint.proceed();
        }
        String module = annotation.value();
        String action = "";
        action = annotation.action().value();
        if(StringTools.isNotEmpty(action)){
            action = MessageFormat.format(",action={0}",action);
        }
        Class<?> targetClass = joinPoint.getTarget().getClass();
        String className = targetClass.getName();
        String target = className + "." + method.getName();
        Object args[] = joinPoint.getArgs();
        log.info("[OvalValidator Request]{}{},target={},dto={}",module,action,target,JSON.toJSON(args));
        if (args == null || args.length == 0) {
            log.warn("该方法缺少校验参数 ");
            return joinPoint.proceed();
        }
        Validator validator = new Validator();
        List<ConstraintViolation> violations = validator.validate(args[0]);
        if (CollectionsTools.isNotEmpty(violations)) {
            return Result.fail(RemoteEnum.ERROR_WITH_EMPTY_PARAM.getIndex(), violations.get(0).getMessage());
        }
        return joinPoint.proceed();
    }
}

#####第三步:相应作用方法添加应用这个注解

    @OvalValidator("前置规则-执行公式")
    @LogMonitor("前置规则-执行公式")
    @Override
    public Result<Object> execExpression(ExpressionCheckDTO dto) {
        Object result;
        try {
            boolean check = checkExpression(dto).getData();
            if(!check){
                return Result.suc("参数不合法,请确保参数包含涉及公式中所有参数");
            }
            Map<String,Object> env = dto.getEnv().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
            AviatorContext ctx = AviatorContext.builder().expression(dto.getExpression()).env(env).cached(true).build();
            result = AviatorExecutor.execute(ctx);
        } catch (Exception e) {
            log.error("前置规则-执行公式异常,dto={}", JSON.toJSON(dto),e);
            return Result.fail(e.getMessage(),1,"前置规则-执行公式失败:"+e.getMessage());
        }
        return Result.suc(result);
    }

###归纳一下

  • 引入oval注解api,并应用entity对象的field字段。
  • 通过oval的Validator的API方法对校验对象进行注解校验。
  • 定义一个注解,应用到需要处理DTO校验的方法作用域。
  • 实现aop业务处理逻辑,返回校验信息。

下面的是我的公众号二维码图片,欢迎关注。
秋夜无霜

发布了46 篇原创文章 · 获赞 27 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/shichen2010/article/details/82630356