我们在对外提供接口服务时,对于参数校验总会存在大量的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业务处理逻辑,返回校验信息。
下面的是我的公众号二维码图片,欢迎关注。