1. 程式人生 > >Spring AOP 通用入參校驗終極版

Spring AOP 通用入參校驗終極版

入參校驗一直是程式中一塊雞肋,食之無味卻又不得不吃。經過幾個版本變更,本次專案上線筆者終於將入參校驗應用了稍微高階一點的寫法。

基調: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了。

方案一缺點:

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;
	}
	
}