天天看點

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

一、@Validated和@Valid對比

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

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

1、分組

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

@Valid:作為标準JSR-303規範,不支援分組的功能。

2、注解地方

@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)。

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

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

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

@validated可以校驗哪些_SpringMvc @Validated注解執行原理
注意,本文的重點代碼來了。
@validated可以校驗哪些_SpringMvc @Validated注解執行原理

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

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

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

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

最後會把BindingResult結果放到ModelAndViewContainer對象中儲存起來,記住

BindingResult.MODEL_KEY_PREFIX

這個key prefix。

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

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

@validated可以校驗哪些_SpringMvc @Validated注解執行原理

看到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專門用于處理方法級别的資料校驗,包括入參校驗和出參校驗。本文就不詳細說明了,感興趣的可以直接參考一下源碼。