背景
大家可能會問,spring MVC支持驗證注解,如常用的hibernate-validator,為什么要自己實現(xiàn)一套呢?
最近做一個APP的服務(wù)端接口,項目中有自己的業(yè)務(wù)返回碼.spring MVC支持的注解驗證器無法設(shè)置驗證不通過的時候的返回碼,各種不方便,所以思前想后還是自己實現(xiàn)了一套.廢話不多說,開始正文.
狀態(tài)碼枚舉
狀態(tài)碼枚舉中有兩個屬性: 狀態(tài)碼 和 對應(yīng)的默認(rèn)消息
public enum ResponseCodeEnum {
_001("001", "用戶未登錄");
/**
* @Fields code : 狀態(tài)碼
*/
private String code;
/**
* @Fields defaultMessage : 默認(rèn)消息
*/
private String defaultMessage;
private ResponseCodeEnum (String code, String defaultMessage) {
this.code = code;
this.defaultMessage = defaultMessage;
}
@JsonValue // com.fasterxml.jackson.annotation.JsonValue, 項目中用了 jackson 做為
// springMVC的JSON轉(zhuǎn)換器,該注解表式這個方法的返回值生成到JSON中,其他忽略
public String getCode() {
return code;
}
public String getDefaultMessage() {
return defaultMessage;
}
}
自定義業(yè)務(wù)異常
業(yè)務(wù)數(shù)據(jù)(客戶端提交的)驗證不過等各種業(yè)務(wù)處理中的不通過,統(tǒng)一使用該異常,該異常被捕獲后會生成統(tǒng)一格式的消息返回到客戶端
public class CustomValidatorException extends RuntimeException {
private static final long serialVersionUID = 5968495544349929856L;
private ResponseCodeEnum statusCode;
private String errorMsg;
public CustomValidatorException (ResponseCodeEnum statusCode, String errorMsg) {
this.statusCode = statusCode;
this.errorMsg = errorMsg;
}
public ResponseCodeEnum getStatusCode() {
return statusCode;
}
public String getErrorMsg() {
return errorMsg;
}
}
驗證器接口
該接口作為驗證器注解必須實現(xiàn)的接口,負(fù)責(zé)真正的驗證
public interface IAnnotationsValidator {
public void doValidator(Object object, Annotation annotation) throws CustomValidatorException ;
}
驗證器注解
驗證器相關(guān)注解定義,首先得有幾個基礎(chǔ)注解
基礎(chǔ)注解
EnableValidator 負(fù)責(zé)開啟驗證,使用了該注解的參數(shù)Bean才會被驗證
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface EnableValidator {
}
CustomValidator 該注解作用于注解(該注解只能被其他注解使用,不能被非注解的類使用),使用了該注解的注解才被做為驗證器注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
@Documented
public @interface CustomValidator {
}
驗證器注解
這里先只寫2個注解吧,其他的可以由其他開發(fā)人員開發(fā)
Required 必傳參數(shù)注解,只有這個注解驗證參數(shù)是否有值,其他注解有值才驗證,沒值直接通過
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited // 子類可以繼承父類的注解
@CustomValidator
public @interface Required {
/**
* @Title: responseCode
* @Description: 驗證失敗(不通過)的code
* @return
*/
ResponseCodeEnum responseCode();
/**
* @Title: validatFailMessage
* @Description: 驗證失敗(不通過)的文字消息,可為空,默認(rèn)使用ResponseCodeEnum對應(yīng)的消息
* @return
*/
String validatFailMessage() default "";
/**
* @Fields validatorSpringBeanName : 此注解對應(yīng)的驗證器的springBean名稱,該名稱在定義注解的時候?qū)懰? */
final String validatorSpringBeanName = "requiredValidator";
}
Required 注解中的幾個屬性是所有驗證器注解都必須有的,大家可能注意到了validatorSpringBeanName , 沒錯,切面就是根據(jù)這個在spring容器中拿驗證器實現(xiàn)的
NotEmpty 用于驗證字符串,List,集合,數(shù)組,Map等不能為空,這里的空不包括null,是null以外的空
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited // 子類可以繼承父類的注解
@CustomValidator
public @interface NotEmpty {
/**
* @Title: responseCode
* @Description: 驗證失敗(不通過)的code
* @return
*/
ResponseCodeEnum responseCode();
/**
* @Title: validatFailMessage
* @Description: 驗證失敗(不通過)的文字消息,可為空,默認(rèn)使用ResponseStatusCodeEnum對應(yīng)的消息
* @return
*/
String validatFailMessage() default "";
/**
* @Fields validatorSpringBeanName : 此注解對應(yīng)的驗證器的springBean名稱,該名稱在定義注解的時候?qū)懰? */
final String validatorSpringBeanName = "notEmptyValidator";
}
除了上面兩個驗證器中的3個必須有的屬性,還可以定義其他的屬性,比如驗證字符串長度的驗證器,可以加一個長度的屬性. 這些屬性可以在驗證器實現(xiàn)中獲取
驗證器(注解)實現(xiàn)
RequiredImpl: Required 的實現(xiàn)
@Component("requiredValidator")
public class RequiredImpl implements IAnnotationsValidator {
@Override
public void doValidator(Object object, Annotation annotation) throws CustomValidatorException {
Required notEmpty = (Required) annotation;
ResponseStatusCodeEnum statusCode = notEmpty.responseStatusCode();
String message = notEmpty.validatFailMessage();
// TODO 獲取驗證器注解中的其他屬性
// TODO 驗證,如果驗證不通過,拋出 CustomValidatorException
}
}
NotEmptyImpl: NotEmpty 的實現(xiàn),具體參考 Required 的實現(xiàn)
AOP
自定義驗證器的核心實現(xiàn),沒有它,上面的東西全是白費
public class ValidatorAdvise {
private static Logger logger = LoggerFactory.getLogger(ValidatorAdvise .class);
public Object validator(ProceedingJoinPoint pjp) {
// 獲取被攔截的方法的參數(shù)
Object[] args = pjp.getArgs();
// 遍歷該方法的所有參數(shù)
if (args != null && args.length > 0) {
for (Object arg : args) {
Class<?> argClassz = arg.getClass();
if (argClassz.getAnnotation(EnableValidator.class) != null) { // 只有當(dāng)該參數(shù)有EnableValidator注解,也就是開啟了驗證才處理
List<Field> fieldList = getAllFields(null, argClassz); // 獲取所有字段
// 遍歷所有字段,并找出有注解的
for (Field field : fieldList) {
// 檢查每個字段的注解,有注解的才處理
Annotation[] fieldAnns = field.getAnnotations();
if (fieldAnns != null && fieldAnns.length > 0) {
// 遍歷該字段的注解,找到驗證器的注解
for (Annotation fieldAnn : fieldAnns) {
try {
// 檢查該注解是否有@CustomValidator,有就說明是驗證器
if (fieldAnn.annotationType().getAnnotation(CustomValidator.class) != null) {
// 通過反射拿驗證器的springBeanName字段,不為null才處理
Field validatorSpringBeanNameFiled = fieldAnn.annotationType().getDeclaredField("validatorSpringBeanName");
if (validatorSpringBeanNameFiled != null) {
// 通過spring拿到驗證器進(jìn)行驗證,先拿驗證器的springBeanName
Object validatorSpringBeanName = validatorSpringBeanNameFiled.get(fieldAnn);
if(StringUtil.isNotNull(validatorSpringBeanName)) {
// 名字有值,從spring容器中拿對應(yīng)的驗證器
IAnnotationsValidator annotationsValidator = SystemApplicationContext.SPRING_CONTEXT.getBean((String)validatorSpringBeanName, IAnnotationsValidator.class);
if(annotationsValidator != null) {
// 驗證器不為空,調(diào)用驗證器
field.setAccessible(true);
try {
annotationsValidator.doValidator(field.get(arg), fieldAnn);
} catch (CustomValidatorException ex) {
String errMsg = null;
if(StringUtil.isNull(ex.getErrorMsg())) {
errMsg = ex.getStatusCode().getDefaultMessage();
} else {
errMsg = ex.getErrorMsg();
}
return makeResponse(ex.getStatusCode(), errMsg);
} catch (Exception ex) {
logger.error("驗證器【{}】里拋出了 CustomValidatorException 以外的異常,請驗證器開發(fā)人員注意!!!", ex, fieldAnn.annotationType());
return makeResponse(ResponseCodeEnum._500, "服務(wù)器內(nèi)部錯誤:====" + ex.getMessage());
}
}
}
}
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
logger.error("驗證器處理切面出了點問題", e);
}
}
}
}
}
}
}
Object ret = null;
try {
ret = pjp.proceed();
} catch (Throwable e) {
throw new RuntimeException("AOP Point Cut ValidatorAdvise Throw Exception :", e);
}
return ret;
}
/**
* @Title: getAllFields
* @Description: 遞歸獲取該類的所有屬性包括父類的爺爺類的...祖宗類的
* @param fieldList
* @param classz
* @return
*/
private List<Field> getAllFields(List<Field> fieldList, Class<?> classz) {
if(classz == null) {
return fieldList;
}
if(fieldList == null) {
fieldList = Arrays.asList(classz.getDeclaredFields()); // 獲得該類的所有字段,但不包括父類的
} else {
Collections.addAll(fieldList, classz.getDeclaredFields()); // 獲得該類的所有字段,但不包括父類的
}
return getAllFields(fieldList, classz.getSuperclass());
}
/**
* @Title: makeResponse
* @Description: 生成統(tǒng)一 Response
* @param statusCode
* @param statusMessage
* @return
*/
private AppApiResponse<?> makeResponse(ResponseCodeEnum statusCode, String statusMessage) {
AppApiResponse<Object> response = new AppApiResponse<>(new Object());
AppApiResponseHeader respHeader = new AppApiResponseHeader();
response.setHeader(respHeader);
respHeader.setStatusCode(statusCode);
respHeader.setStatusMessage(statusMessage);
return response;
}
}
捕獲異常,生成統(tǒng)一格式響應(yīng)
利用springMVC的@ControllerAdvice捕獲所有來自 Controller 的異常
@ControllerAdvice(basePackages = "org.test.appApi.actions")
public class ErrorHandlingControllerAdvice {
private static Logger logger = LoggerFactory.getLogger(ErrorHandlingControllerAdvice.class);
/**
* @Title: handleValidationError
* @Description: 處理表單驗證,業(yè)務(wù)異常
* @param ex
* @return
*/
@ExceptionHandler(CustomValidatorException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AppApiResponse<?> handleValidationError(CustomValidatorException ex) {
String errMsg = null;
if(StringUtil.isNull(ex.getErrorMsg())) {
errMsg = ex.getStatusCode().getDefaultMessage();
} else {
errMsg = ex.getErrorMsg();
}
return makeResponse(ex.getStatusCode(), errMsg);
}
/**
* @Title: handleValidationError
* @Description: 處理其他異常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public AppApiResponse<?> handleValidationError(Exception ex) {
logger.error("服務(wù)器內(nèi)部錯誤:====", ex);
return makeResponse(ResponseCodeEnum._500, "服務(wù)器內(nèi)部錯誤:====" + ex.getMessage());
}
private AppApiResponse<?> makeResponse(ResponseCodeEnum statusCode, String statusMessage) {
AppApiResponse<Object> response = new AppApiResponse<>(new Object());
AppApiResponseHeader respHeader = new AppApiResponseHeader();
response.setHeader(respHeader);
respHeader.setStatusCode(statusCode);
respHeader.setStatusMessage(statusMessage);
return response;
}
}
配制自定義驗證器切面
springMVC的配制文件中增加
<bean id="validatorAdvise" class="org.test.appApi.actions.validator.advises.ValidatorAdvise" />
<aop:config>
<aop:aspect id="validatorAop" ref="validatorAdvise">
<aop:pointcut id="validator" expression="execution(* org.test.appApi.actions..*Action.*(..))
and !execution(* org.test.appApi.actions..*Action.initBinder(..))
and !execution(* org.test.appApi.actions..*Action.set*(..))
and !execution(* org.test.appApi.actions..*Action.get*(..))" />
<aop:around pointcut-ref="validator" method="validator" />
</aop:aspect>
</aop:config>
使用注解
使用注解很簡單,springMVC的控制器中的方法可以定義任意類型的參數(shù),把各種參數(shù)放到一個java bean中,并在該bean使用類注解 @EnableValidator開啟驗證,并在需要驗證的類屬性上使用對應(yīng)的驗證器注解就行,驗證器注解可以多個混合使用
寫在結(jié)束
到這里,自定義驗證器的開發(fā)就完成了.也許大家有更好的辦法,歡迎討論.也許spring MVC可以有辦法實現(xiàn)我想做的但我不知道,也歡迎大家指出.