一劳永逸——自实现注解完成入参校验

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Daybreak1209/article/details/82747672

入参校验一直是程序中一块鸡肋,食之无味却又不得不吃。经过几个版本变更,本次项目上线笔者终于将入参校验应用了稍微高级一点的写法。

基调:hibernate.validator

实现-低配版

1、引入pom

<dependency>

    <groupId>org.hibernate</groupId>

    <artifactId>hibernate-validator</artifactId>

    <version>5.0.2.Final</version>

</dependency>

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 <T> void validate(T obj) {
        Set<ConstraintViolation<T>> 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<TradeDto> 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了。

扫描二维码关注公众号,回复: 3569303 查看本文章

方案一缺点:

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 <T> Result validate(T... obj) {
        for (int i = 0; i < obj.length; i++) {
            if (obj[i] == null) {
                return ResultWrapper.fail(ErrorCode.PARAMETER_IS_NULL);
            }
            Set<ConstraintViolation<T>> 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<TradeDto> 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、配置文件(避免有些面试官刨根问底,知道即可)

<aop:config expose-proxy="true">  
    <aop:aspect ref="aopBean" order="0">    
        <aop:pointcut id="testPointcut" expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>    
        <aop:around pointcut-ref="testPointcut" method="doAround" />    
        </aop:aspect>    
</aop:config>  

3、显示实现Order接口

@Component
@Aspect
public class MessageQueueAopAspect1 implements Ordered{
    @Override
	public int getOrder() {
		return 2;
	}
	
}

猜你喜欢

转载自blog.csdn.net/Daybreak1209/article/details/82747672