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