入参校验一直是程序中一块鸡肋,食之无味却又不得不吃。经过几个版本变更,本次项目上线笔者终于将入参校验应用了稍微高级一点的写法。
基调:hibernate.validator
1、引入pom
org.hibernate
hibernate-validator
5.0.2.Final
2、封装校验类
public class ValidationUtils {
private static ILog logger = LogFactory.getLogger(ValidationUtils.class);
/**
* 使用hibernate的注解来进行验证
*/
private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
public static void validate(T obj) {
Set> constraintViolations = validator.validate(obj);
// 抛出检验异常
if (constraintViolations.size() > 0) {
logger.warn("param invalidate fail :{}", constraintViolations.iterator().next().getMessage());
System.out.println(constraintViolations.iterator().next().getMessage());
}
}
}
3、应用
如对TradeDto进行校验则在TradeDto中必传的参数上增加@NotNull注解,并定义返回msg
public class TradeDto {
@NotNull(message = "订单ID为空")
private Long orderId;
@NotNull(message = "业务线ID为空")
private Long businessId;
.......
}
方法处由原挨个判断改为调用封装类校验。
public Result createTrade(TradeDto tradeDto) {
if (tradeDto == null || tradeDto.getOrderId() == null || tradeDto.getBusinessId() == null) {
return ResultWrapper.fail(ErrorCode.PARAMETER_CANNOT_NULL);
}
替换为:
ValidationUtils.validate(tradeDto);
4、结果
入参tradeDto中orderId为空时,返回“订单ID为空”的msg
******************************************************* 方案一完毕***********************************************************************
如果就这么结束了 未免也太low了。
方案一缺点:
1、每次入参校验都要写一段:ValidationUtils.....(简单但不太高级)
2、ValidationUtils封装类只校验单个dto,那如果入参有Long+Dto等组合情况呢?难不成调两遍ValidationUtils?(粗暴又低级)
3、validate方法返回个void,那调用处是接着执行呢,还是return呢?(返回值应结合业务逻辑)
基调:AOP+注解;方法可变参数;返回值封装
1、注解类封装
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Valid {
}
2、切面类封装
@Aspect
@Component
@Order(2)
public class ValidAspect {
/**
* 获取参数进行入参校验
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("execution(* com.trade.component..*.*(..)) && @annotation(com.trade.aspect.Valid))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Result validate = ValidationUtils.validate(args);
if (validate.isSuccess()) {
return joinPoint.proceed();
}
return validate;
}
}
注:注解order(1)后面解释
3、校验方法优化(可变参数+业务有意义返回值)
public static Result validate(T... obj) {
for (int i = 0; i < obj.length; i++) {
if (obj[i] == null) {
return ResultWrapper.fail(ErrorCode.PARAMETER_IS_NULL);
}
Set> constraintViolations = validator.validate(obj[i]);
if (constraintViolations.size() <= 0) {
continue;
} else {
logger.warn("param invalidate fail :{}", constraintViolations.iterator().next().getMessage());
System.out.println(constraintViolations.iterator().next().getMessage());
return ResultWrapper.fail(constraintViolations.iterator().next().getMessage());
}
}
return ResultWrapper.success();
}
4、应用
@Valid
@Log
@Override
public Result createTrade(TradeDto tradeDto) {..}
5、结果
添加@Valid注解执行方法入参校验,参数类型支持多个(基础类型+Dto组合传参)。
AOP执行顺序控制
之前博客写过使用AOP进行log入参出参统一日志输出的实现,如上createTrade方法,已有两个zidi自定义AOP作用于gaif该方法,那如何控制AOP执行顺序呢?@Order(int)注解就是这个作用。int值越小该切点yuex越先执行。所以Log切点xian项目应用@Order(1),先进行入参打印,而后执行参数校验@Valid。具体其他控制方法总结如下:
1、@Order(最直观简单)
2、配置文件(避免有些面试官刨根问底,知道即可)
3、显示实现Order接口
@Component
@Aspect
public class MessageQueueAopAspect1 implements Ordered{
@Override
public int getOrder() {
return 2;
}
}