前言
參數(shù)校驗(yàn)箭启,相信每個(gè)后端開發(fā)人員都有接觸。一般情況下前后端都會(huì)對(duì)數(shù)據(jù)進(jìn)行雙重校驗(yàn)蛉迹,保證正確性傅寡。比如某個(gè)參數(shù)不可為null,手機(jī)號(hào)格式不對(duì),等等荐操。
本文的重點(diǎn)在接口級(jí)別校驗(yàn)芜抒,起始于工作中的微服務(wù)接口參數(shù)檢查。大腦最省力原則告訴我托启,能不一個(gè)一個(gè)手動(dòng)寫宅倒,咱就堅(jiān)持抽象出來(lái)。
通常采用的是JSR303規(guī)范來(lái)做校驗(yàn)屯耸,Hibernate validator是JSR303規(guī)范的一種很好的實(shí)現(xiàn)拐迁。
依賴引入(Maven)
略(......)見諒
接口返回
抽象,那么執(zhí)行接口方法之前要check參數(shù)疗绣,不符合場(chǎng)景直接返回結(jié)果唠亚。首先,我們抽象一個(gè)接口返回包裝類持痰。相信這個(gè)已經(jīng)不稀奇灶搜,即使沒有參數(shù)校驗(yàn),大家N年前就已經(jīng)做了工窍。
public class MessageReturn<T> {
/** 狀態(tài):默認(rèn)0 成功割卖;其他為失敗 */
private int status;
/** 響應(yīng)結(jié)果*/
private T result;
/** 相應(yīng)描述*/
private String message;
// 余下部分已省略
}
自定義注解
AOP切點(diǎn)
/**
* 參數(shù)校驗(yàn)注解類
*
* @author wangzhuhua
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Order(AspectOrderConstant.Validator)
@Documented
@Inherited
public @interface HValidate {
/**默認(rèn)錯(cuò)誤碼*/
// 這個(gè)default 是上面的MessageReturn
int errorCode() default MessageUtil.BUSY;
}
校驗(yàn)分組
這里自定義了分組校驗(yàn)規(guī)則
/**
* 參數(shù)校驗(yàn)分組注解類
*
* @author wangzhuhua
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Constraint(validatedBy = HValidateGroup.HValidateGroupValidator.class)
@Documented
public @interface HValidateGroup {
//默認(rèn)錯(cuò)誤消息
String message() default "";
//分組
Class<?>[] groups() default { };
//負(fù)載
Class<? extends Payload>[] payload() default { };
//校驗(yàn)分組
Class<?>[] value() default {};
class HValidateGroupValidator implements ConstraintValidator<HValidateGroup, Object> {
private Class<?>[] groups;
private ValidatorFactory validatorFactory;
public void initialize(HValidateGroup hValidateGroup) {
this.groups = hValidateGroup.value();
validatorFactory = ValidatorInterceptor.ValidatorFactory;
}
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
if (object == null) {
return true;
}
Set<ConstraintViolation<Object>> validResult = validatorFactory.getValidator().validate(object, groups);
if(validResult.size() == 0){
return true;
}
Iterator<ConstraintViolation<Object>> iterator = validResult.iterator();
while(iterator.hasNext()){
ConstraintViolation violation = iterator.next();
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext.buildConstraintViolationWithTemplate(violation.getMessageTemplate())
.addPropertyNode(violation.getPropertyPath().toString())
.addConstraintViolation();
}
return false;
}
}
}
AOP攔截校驗(yàn)
/**
* 參數(shù)校驗(yàn)配置
*
* @author wangzhuhua
**/
@Component
@Aspect
public class ValidatorInterceptor {
/**
* true:快速校驗(yàn)?zāi)J剑琭alse:全部校驗(yàn)?zāi)J? */
@Value("${hibernate.validator.failFast:false}")
private boolean validateModel;
/**
* 解決組合切點(diǎn)參數(shù)注入只適用于切點(diǎn)級(jí)別高的效應(yīng)
*/
ThreadLocal<HValidate> currentAnnatation = new ThreadLocal<>();
/**
* 校驗(yàn)器工廠
*/
private ValidatorFactory validatorFactory;
static ValidatorFactory ValidatorFactory;
/**
* 構(gòu)建校驗(yàn)工廠
*/
@PostConstruct
public void initValidator() {
validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("MyMessages")))
.failFast(validateModel)
.buildValidatorFactory();
ValidatorInterceptor.ValidatorFactory = validatorFactory;
}
/**
* 切點(diǎn)
*/
@Pointcut("@within(com.sstc.hmis.util.validator.HValidate)")
public void pointcutWithin() {
}
/**
* 切點(diǎn)
*/
@Pointcut("@annotation(com.sstc.hmis.util.validator.HValidate)")
public void pointcutAnnotation() {
}
/**
*
* @param proceedingJoinPoint
* @param hValidate
* @return
*/
@Around(value = "pointcutWithin() && @within(hValidate)")
public Object pointcutWithin(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
setCurrentAnnatation(hValidate);
return process(proceedingJoinPoint);
}
/**
*
* @param proceedingJoinPoint
* @param hValidate
* @return
*/
@Around(value = "pointcutAnnotation() && @annotation(hValidate)")
public Object pointcutAnnotation(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
setCurrentAnnatation(hValidate);
return process(proceedingJoinPoint);
}
/**
* 校驗(yàn)切點(diǎn)
*
* @param proceedingJoinPoint 切點(diǎn)對(duì)象
* @return 接口響應(yīng)
* @throws Throwable
*/
@Around("pointcutWithin()")
public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 獲得的方法
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
// 獲得切入目標(biāo)對(duì)象
Object target = proceedingJoinPoint.getThis();
// 獲得切入方法參數(shù)
Object[] args = proceedingJoinPoint.getArgs();
// 執(zhí)行驗(yàn)證
Set<ConstraintViolation<Object>> validResult = validateParameters(target, method, args);
// 參數(shù)不合法返回
if (validResult.size() > 0) {
return processConstraintViolations(validResult);
}
return proceedingJoinPoint.proceed();
}
/**
* 校驗(yàn)方法參數(shù)
*
* @param target 校驗(yàn)方法所在對(duì)象
* @param method 將校驗(yàn)的方法
* @param args 參數(shù)
* @return 校驗(yàn)結(jié)果
*/
public Set<ConstraintViolation<Object>> validateParameters(Object target, Method method, Object[] args) {
// 校驗(yàn)結(jié)果
Set<ConstraintViolation<Object>> result = new HashSet<>();
// 先驗(yàn)證默認(rèn)Validator校驗(yàn)范圍
Validator validator = validatorFactory.getValidator();
ExecutableValidator executableValidator = validator.forExecutables();
Set<ConstraintViolation<Object>> validResult = executableValidator.validateParameters(target, method, args);
result.addAll(validResult);
return result;
}
/**
* 驗(yàn)證信息轉(zhuǎn)MessageReturn
*
* @param violations 驗(yàn)證信息
* @return 返回消息
*/
public MessageReturn<Object> processConstraintViolations(Set<ConstraintViolation<Object>> violations) {
// 僅取第一條錯(cuò)誤
StringBuilder sb = new StringBuilder();
Iterator<ConstraintViolation<Object>> iterator = violations.iterator();
if (iterator.hasNext()) {
sb.append(iterator.next().getMessage()).append(";");
}
// 構(gòu)建校驗(yàn)錯(cuò)誤提示信息(使用已知消息提示)
MessageReturn<Object> messageReturn = new MessageReturn<Object>();
MessageReturn.setStatus(getCurrentAnnatation().errorCode());
MessageReturn.setMessage(sb.toString());
return MessageReturn;
}
/**
* 獲取 校驗(yàn)器工廠
*
* @return validatorFactory 校驗(yàn)器工廠
*/
public ValidatorFactory getValidatorFactory() {
return this.validatorFactory;
}
/**
* 設(shè)置 校驗(yàn)器工廠
*
* @param validatorFactory 校驗(yàn)器工廠
*/
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validatorFactory = validatorFactory;
}
/**
* 獲取 true:快速校驗(yàn)?zāi)J交汲琭alse:全部校驗(yàn)?zāi)J? *
* @return validateModel true:快速校驗(yàn)?zāi)J脚羲荩琭alse:全部校驗(yàn)?zāi)J? */
public boolean isValidateModel() {
return this.validateModel;
}
/**
* 設(shè)置 true:快速校驗(yàn)?zāi)J剑琭alse:全部校驗(yàn)?zāi)J? *
* @param validateModel true:快速校驗(yàn)?zāi)J窖吐兀琭alse:全部校驗(yàn)?zāi)J? */
public void setValidateModel(boolean validateModel) {
this.validateModel = validateModel;
}
/**
* 獲取 注解對(duì)象
*
* @return currentAnnatation 注解對(duì)象
*/
public HValidate getCurrentAnnatation() {
return this.currentAnnatation.get();
}
/**
* 設(shè)置 注解對(duì)象
*
* @param hValidate 注解對(duì)象
*/
public void setCurrentAnnatation(HValidate hValidate) {
this.currentAnnatation.set(hValidate);
}
}
使用示例
接口定義
實(shí)際工作中使用到Spring Cloud丙挽,就用這個(gè)做個(gè)示例了
@RequestMapping(value = "/add", method = RequestMethod.POST)
MessageReturn<CustomObject> add(@RequestBody @HValidateGroup({CustomObjectGroup.Add.class }) CustomObject customObject);
接口實(shí)現(xiàn)類
@RestController
// 這里作為切點(diǎn)進(jìn)入
@HValidate(errorCode = 10101000)
// 下面這個(gè)是未知異常catch,請(qǐng)忽略
@ErrorHandler(errorCode = 20101099)
public class CustomServiceImpl implements CustomService {
@Override
// @HValidate(errorCode = 10101099)
// 有需要的場(chǎng)景匀借,這里的級(jí)別更高颜阐,出現(xiàn)不同的錯(cuò)誤碼
public MessageReturn<CustomObject> add(@RequestBody CustomObject customObject) {
// do something
return xxxx;
}
校驗(yàn)規(guī)則
/**
* Demo
*
* @author wangzhuhua
**/
public class CustomObject {
/** 標(biāo)識(shí) */
@NotEmpty(message = "ID不可為空", groups = { CustomObjectGroup.Update.class })
private String id;
/** 名稱 */
@NotEmpty(message = "名稱不能為空", groups = { CustomObjectGroup.Add.class,
CustomObjectGroup.Update.class })
private String name;
}
這樣拓展了Hibernate validator,用起來(lái)方便吓肋,即可分組凳怨,也可自定義校驗(yàn)規(guī)則。