通過(guò)Spring AOP實(shí)現(xiàn)Spring MVC自定義注解驗(yàn)證器

背景

大家可能會(huì)問(wèn),spring MVC支持驗(yàn)證注解,如常用的hibernate-validator,為什么要自己實(shí)現(xiàn)一套呢?
最近做一個(gè)APP的服務(wù)端接口,項(xiàng)目中有自己的業(yè)務(wù)返回碼.spring MVC支持的注解驗(yàn)證器無(wú)法設(shè)置驗(yàn)證不通過(guò)的時(shí)候的返回碼,各種不方便,所以思前想后還是自己實(shí)現(xiàn)了一套.廢話不多說(shuō),開(kāi)始正文.

狀態(tài)碼枚舉

狀態(tài)碼枚舉中有兩個(gè)屬性: 狀態(tài)碼 和 對(duì)應(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, 項(xiàng)目中用了 jackson 做為 
                          // springMVC的JSON轉(zhuǎn)換器,該注解表式這個(gè)方法的返回值生成到JSON中,其他忽略
    public String getCode() {
        return code;
    }

    public String getDefaultMessage() {
        return defaultMessage;
    }
}

自定義業(yè)務(wù)異常

業(yè)務(wù)數(shù)據(jù)(客戶端提交的)驗(yàn)證不過(guò)等各種業(yè)務(wù)處理中的不通過(guò),統(tǒng)一使用該異常,該異常被捕獲后會(huì)生成統(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;
    }

}

驗(yàn)證器接口

該接口作為驗(yàn)證器注解必須實(shí)現(xiàn)的接口,負(fù)責(zé)真正的驗(yàn)證

public interface IAnnotationsValidator {

    public void doValidator(Object object, Annotation annotation) throws CustomValidatorException ;
    
}

驗(yàn)證器注解

驗(yàn)證器相關(guān)注解定義,首先得有幾個(gè)基礎(chǔ)注解

基礎(chǔ)注解

EnableValidator 負(fù)責(zé)開(kāi)啟驗(yàn)證,使用了該注解的參數(shù)Bean才會(huì)被驗(yàn)證
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface EnableValidator {

}
CustomValidator 該注解作用于注解(該注解只能被其他注解使用,不能被非注解的類(lèi)使用),使用了該注解的注解才被做為驗(yàn)證器注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
@Documented
public @interface CustomValidator {

}

驗(yàn)證器注解

這里先只寫(xiě)2個(gè)注解吧,其他的可以由其他開(kāi)發(fā)人員開(kāi)發(fā)

Required 必傳參數(shù)注解,只有這個(gè)注解驗(yàn)證參數(shù)是否有值,其他注解有值才驗(yàn)證,沒(méi)值直接通過(guò)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited  // 子類(lèi)可以繼承父類(lèi)的注解
@CustomValidator
public @interface Required {

    /**
     * @Title: responseCode
     * @Description: 驗(yàn)證失敗(不通過(guò))的code
     * @return
     */
    ResponseCodeEnum responseCode();
    
    /**
     * @Title: validatFailMessage
     * @Description: 驗(yàn)證失敗(不通過(guò))的文字消息,可為空,默認(rèn)使用ResponseCodeEnum對(duì)應(yīng)的消息
     * @return
     */
    String validatFailMessage() default "";
    
    /**
     * @Fields validatorSpringBeanName : 此注解對(duì)應(yīng)的驗(yàn)證器的springBean名稱(chēng),該名稱(chēng)在定義注解的時(shí)候?qū)懰?     */
    final String validatorSpringBeanName = "requiredValidator";
    
}

Required 注解中的幾個(gè)屬性是所有驗(yàn)證器注解都必須有的,大家可能注意到了validatorSpringBeanName , 沒(méi)錯(cuò),切面就是根據(jù)這個(gè)在spring容器中拿驗(yàn)證器實(shí)現(xiàn)的

NotEmpty 用于驗(yàn)證字符串,List,集合,數(shù)組,Map等不能為空,這里的空不包括null,是null以外的空
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited  // 子類(lèi)可以繼承父類(lèi)的注解
@CustomValidator
public @interface NotEmpty {

    /**
     * @Title: responseCode
     * @Description: 驗(yàn)證失敗(不通過(guò))的code
     * @return
     */
    ResponseCodeEnum responseCode();
    
    /**
     * @Title: validatFailMessage
     * @Description: 驗(yàn)證失敗(不通過(guò))的文字消息,可為空,默認(rèn)使用ResponseStatusCodeEnum對(duì)應(yīng)的消息
     * @return
     */
    String validatFailMessage() default "";
    
    /**
     * @Fields validatorSpringBeanName : 此注解對(duì)應(yīng)的驗(yàn)證器的springBean名稱(chēng),該名稱(chēng)在定義注解的時(shí)候?qū)懰?     */
    final String validatorSpringBeanName = "notEmptyValidator";
    
}

除了上面兩個(gè)驗(yàn)證器中的3個(gè)必須有的屬性,還可以定義其他的屬性,比如驗(yàn)證字符串長(zhǎng)度的驗(yàn)證器,可以加一個(gè)長(zhǎng)度的屬性. 這些屬性可以在驗(yàn)證器實(shí)現(xiàn)中獲取

驗(yàn)證器(注解)實(shí)現(xiàn)

RequiredImpl: Required 的實(shí)現(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 獲取驗(yàn)證器注解中的其他屬性
        // TODO 驗(yàn)證,如果驗(yàn)證不通過(guò),拋出 CustomValidatorException 
    }

}
NotEmptyImpl: NotEmpty 的實(shí)現(xiàn),具體參考 Required 的實(shí)現(xiàn)

AOP

自定義驗(yàn)證器的核心實(shí)現(xiàn),沒(méi)有它,上面的東西全是白費(fèi)

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注解,也就是開(kāi)啟了驗(yàn)證才處理
            List<Field> fieldList = getAllFields(null, argClassz); // 獲取所有字段
            // 遍歷所有字段,并找出有注解的
            for (Field field : fieldList) {
            // 檢查每個(gè)字段的注解,有注解的才處理
            Annotation[] fieldAnns = field.getAnnotations();
            if (fieldAnns != null && fieldAnns.length > 0) {
                // 遍歷該字段的注解,找到驗(yàn)證器的注解
                for (Annotation fieldAnn : fieldAnns) {
                try {
                    // 檢查該注解是否有@CustomValidator,有就說(shuō)明是驗(yàn)證器
                    if (fieldAnn.annotationType().getAnnotation(CustomValidator.class) != null) {
                    // 通過(guò)反射拿驗(yàn)證器的springBeanName字段,不為null才處理
                    Field validatorSpringBeanNameFiled = fieldAnn.annotationType().getDeclaredField("validatorSpringBeanName");
                    if (validatorSpringBeanNameFiled != null) {
                        // 通過(guò)spring拿到驗(yàn)證器進(jìn)行驗(yàn)證,先拿驗(yàn)證器的springBeanName
                        Object validatorSpringBeanName = validatorSpringBeanNameFiled.get(fieldAnn);
                        if(StringUtil.isNotNull(validatorSpringBeanName)) {
                        // 名字有值,從spring容器中拿對(duì)應(yīng)的驗(yàn)證器
                        IAnnotationsValidator annotationsValidator = SystemApplicationContext.SPRING_CONTEXT.getBean((String)validatorSpringBeanName, IAnnotationsValidator.class);
                        if(annotationsValidator != null) {
                            // 驗(yàn)證器不為空,調(diào)用驗(yàn)證器
                            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("驗(yàn)證器【{}】里拋出了 CustomValidatorException 以外的異常,請(qǐng)驗(yàn)證器開(kāi)發(fā)人員注意!!!", ex, fieldAnn.annotationType());
                                return makeResponse(ResponseCodeEnum._500, "服務(wù)器內(nèi)部錯(cuò)誤:====" + ex.getMessage());
                            }
                        }
                        }
                    }
                    }
                } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
                    logger.error("驗(yàn)證器處理切面出了點(diǎn)問(wèn)題", 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: 遞歸獲取該類(lèi)的所有屬性包括父類(lèi)的爺爺類(lèi)的...祖宗類(lèi)的
     * @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());  // 獲得該類(lèi)的所有字段,但不包括父類(lèi)的
    } else {
        Collections.addAll(fieldList, classz.getDeclaredFields());  // 獲得該類(lèi)的所有字段,但不包括父類(lèi)的
    }
    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捕獲所有來(lái)自 Controller 的異常

@ControllerAdvice(basePackages = "org.test.appApi.actions")
public class ErrorHandlingControllerAdvice {

    private static Logger logger = LoggerFactory.getLogger(ErrorHandlingControllerAdvice.class);
    
    /**
     * @Title: handleValidationError
     * @Description: 處理表單驗(yàn)證,業(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)部錯(cuò)誤:====", ex);
        return makeResponse(ResponseCodeEnum._500, "服務(wù)器內(nèi)部錯(cuò)誤:====" + 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;
    }
    
}

配制自定義驗(yàn)證器切面

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>

使用注解

使用注解很簡(jiǎn)單,springMVC的控制器中的方法可以定義任意類(lèi)型的參數(shù),把各種參數(shù)放到一個(gè)java bean中,并在該bean使用類(lèi)注解 @EnableValidator開(kāi)啟驗(yàn)證,并在需要驗(yàn)證的類(lèi)屬性上使用對(duì)應(yīng)的驗(yàn)證器注解就行,驗(yàn)證器注解可以多個(gè)混合使用

寫(xiě)在結(jié)束

到這里,自定義驗(yàn)證器的開(kāi)發(fā)就完成了.也許大家有更好的辦法,歡迎討論.也許spring MVC可以有辦法實(shí)現(xiàn)我想做的但我不知道,也歡迎大家指出.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,272評(píng)論 6 342
  • 前言:由于在第一次面試中吃了暗虧,考到了SpringMVC的@RequestMapping和@ResponseBo...
    天譴殘魂閱讀 4,642評(píng)論 3 17
  • 昨晚看了《從你的全世界路過(guò)》,看完之后,沒(méi)有一點(diǎn)感覺(jué)。不禁開(kāi)始懷疑,是不是我有點(diǎn)鐵石心腸??磁笥讶υ谒⑦@部電影已經(jīng)...
    菜菜小仙女閱讀 182評(píng)論 0 0
  • 今天學(xué)習(xí)了孫路弘老師《媽媽教的數(shù)學(xué)》的第四講:默想是智力發(fā)展的敵人。學(xué)習(xí)的過(guò)程中,說(shuō)出思考過(guò)程是啟發(fā)智力...
    小妮兒的簡(jiǎn)生活閱讀 99評(píng)論 0 0

友情鏈接更多精彩內(nèi)容