SpringMvc @Validated注解執行原理


@Validated和@Valid對比

Spring Validation驗證框架對參數的驗證機制提供了@Validated(Spring's JSR-303規范,是標准JSR-303的一個變種),javax提供了@Valid(標准JSR-303規范),配合BindingResult可以直接提供參數驗證結果。

在檢驗Controller的入參是否符合規范時,使用@Validated或者@Valid在基本驗證功能上沒有太多區別。但是在分組、注解地方、嵌套驗證等功能上兩個有所不同。

分組

@Validated:提供了一個分組功能,可以在入參驗證時,根據不同的分組采用不同的驗證機制,可參考高效使用hibernate-validator校驗框架

@Valid:作為標准JSR-303規范,不支持分組的功能。

注解地方

@Validated:可以用在類型、方法和方法參數上。但是不能用在成員屬性(字段)上。

@Valid:可以用在方法、構造函數、方法參數和成員屬性(字段)上。

兩者是否能用於成員屬性(字段)上直接影響能否提供嵌套驗證的功能。

SpringMvc接口參數校驗原理

springmvc接口方法中注有@Validated或@Valid參數是如何校驗的呢?怎么就能把參數綁定之后的校驗結果給到BindingResult實例呢?

@RestController
public class TestController {
    @RequestMapping("/xxx/yyy")
    public void test1(@Validated Test test, BindingResult bindingResult) {
        doSomething();
    }

    @RequestMapping("/yyy/zzz")
    public void test1(@Valid Test test, BindingResult bindingResult) {
        doSomething();
    }
}

其實如果你對springmvc的方法參數解析器(HandlerMethodArgumentResolver)了解一些,就應該知道參數校驗這塊肯定是在對應的方法參數解析器里執行的。如下是@RequestBody注解對應的參數解析器(RequestResponseBodyMethodProcessor)。

直接定位到resolveArgument這個方法,很明顯,該方法是根據參數類型找到支持的消息轉換器(Message Converter),然后從request body中讀取信息,最后轉換成對應的參數實體。

WebDataBinder主要是完成對象屬性校驗的。如果你熟悉@ModelAttribute注解對應的方法參數解析器(ModelAttributeMethodProcessor),是先通過WebDataBinder進行入參屬性綁定,然后再進行校驗。

 

注意,本文的重點代碼來了。

簡單說一下validateIfApplicable方法的邏輯,遍歷當前參數methodParam所有的注解,如果注解是@Validated或注解的名字以‘Valid’開頭,則使用WebDataBinder對象執行校驗邏輯。

isBindExceptionRequired方法,說的通俗一點,就是要不要拋出異常。怎么判斷呢?如果當前參數后面還有參數並且參數類型是Errors(BindingResult繼承Errors)則不拋出異常。

最后會把BindingResult結果放到ModelAndViewContainer對象中保存起來,記住BindingResult.MODEL_KEY_PREFIX這個key prefix。

mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

都說到這里了,BindingResult結果也已經拿到了,該怎么傳遞給方法中參數呢?會不會有BindingResult參數類型的參數解析器呢?咦,還真他娘的有呀。。。

看到ErrorsMethodArgumentResolver這個參數解析器的注釋和源碼,的確是針對BindingResult這種參數類型的。BindingResult.MODEL_KEY_PREFIX這個常量在這里出現了,在ModelAndViewContainer對象中拿到BindingResult對象。注意最后面拋出了一個IllegalStateException異常,也就是在ModelAndViewContainer對象中沒有找到BindingResult對象的時候才會拋出這個異常,什么情況找不到?

@RequestMapping("/xxx/yyy")
public void test1(BindingResult bindingResult, @Validated @RequestBody Test test) {
    doSomething();
}

上述寫法就會出現IllegalStateException異常。因為springmvc解析參數的時候是按照順序, 所以BindingResult類型的參數一定要放在校驗實體的后面。

@Validated方法級校驗

注意:方法級別的入參有可能是各種平鋪的參數、也可能是一個或者多個對象。

下面這個例子就是@Validated注解方法級的校驗demo,不過需要配合MethodValidationPostProcessor這個后置處理器使用,需要我們手動注冊一下。

@Validated(Default.class)
public interface HelloService {
    Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
}

public class HelloServiceImpl implements HelloService {
    @Override
    public Object hello(Integer id, String name) {
        return null;
    }
}

簡單說一下MethodValidationPostProcessor。

Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));

protected Advice createMethodValidationAdvice(Validator validator) {
    return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}

validatedAnnotationType變量默認是就是@Validated注解類型,所以創建的切面Pointcut對象是切入所有注有@Validated注解的類的所有方法。

Spring環境里創建動態代理核心指定一個或多個advisor,advisor由pointcut和advice組成,pointcut已經創建,advice實例即MethodValidationInterceptor。很明顯,MethodValidationInterceptor專門用於處理方法級別的數據校驗,包括入參校驗和出參校驗,本文就不詳細說明了,感興趣的可以直接參考一下源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM