小架構step系列26:Spring提供的validator

1 概述

對于Web服務,需要對請求的參數進行校驗,可以對不合法的參數進行提示,提高用戶體驗。也可以防止有人惡意用一些非法的參數對網站造成破壞。如果是對每個參數都寫一段代碼來判斷值是否合法,那校驗的代碼就很多,也很繁瑣。Spring提供了一套校驗機制,先來了解一下。

2 原理

2.1 初始化mvcValidator

初始化mvcValidator,mvcValidator是一個SpringValidatorAdapter,其里面的targetValidator是真正執行validate校驗的對象,初始化mvcValidator的主要目的就是要把targetValidator組裝好。

// 源碼位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
// EnableWebMvcConfiguration是帶@Configuration注解的類,當開啟了WebMvc就會加載
@Bean
public Validator mvcValidator() {if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {return super.mvcValidator();}// 1. 調getValidator()獲取Validator//    EnableWebMvcConfiguration繼承DelegatingWebMvcConfiguration,調的是DelegatingWebMvcConfiguration的getValidator()return ValidatorAdapter.get(getApplicationContext(), getValidator());
}// 源碼位置:org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
protected Validator getValidator() {// 2. configures就是平時熟悉的WebMvcConfigurer的封裝(WebMvcConfigurerComposite)return this.configurers.getValidator();
}// 源碼位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite
public Validator getValidator() {Validator selected = null;for (WebMvcConfigurer configurer : this.delegates) {// 3. 如果定義了WebMvcConfigurer,則實際調WebMvcConfigurer的getValidator()//    WebMvcConfigurer是Spring提供的常用的擴展方式,可通過它來增加Validator,此時沒有定義返回的是nullValidator validator = configurer.getValidator();if (validator != null) {if (selected != null) {throw new IllegalStateException("No unique Validator found: {" +selected + ", " + validator + "}");}selected = validator;}}return selected;
}// 回到EnableWebMvcConfiguration的mvcValidator(),處理ValidatorAdapter.get()
// 源碼位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
@Bean
public Validator mvcValidator() {if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {return super.mvcValidator();}// 1. 調getValidator()獲取Validator// 4. getValidator()獲取的Validator作為默認的,繼續由ValidatorAdapter.get()處理return ValidatorAdapter.get(getApplicationContext(), getValidator());
}// 源碼位置:org.springframework.boot.autoconfigure.validation.ValidatorAdapter
public static Validator get(ApplicationContext applicationContext, Validator validator) {// 如果獲取到了Validator,優先封裝到ValidatorAdapter里面并返回ValidatorAdapter,這里主要看沒有獲取到Validator的場景if (validator != null) {return wrap(validator, false);}// 5. 獲取一個存在的,如果沒有就創建一個return getExistingOrCreate(applicationContext);
}
private static Validator getExistingOrCreate(ApplicationContext applicationContext) {// 6. 先獲取已經存在的ValidatorValidator existing = getExisting(applicationContext);if (existing != null) {return wrap(existing, true);}return create(applicationContext);
}
private static Validator getExisting(ApplicationContext applicationContext) {try {// 7. 獲取實現了org.springframework.validation.Validator接口的bean,這里主要看獲取不到的場景,也就是Spring默認沒有注入實現了該接口的beanjavax.validation.Validator validator = applicationContext.getBean(javax.validation.Validator.class);if (validator instanceof Validator) {return (Validator) validator;}return new SpringValidatorAdapter(validator);}catch (NoSuchBeanDefinitionException ex) {return null; // 獲取不到從這里返回null}
}// 回到ValidatorAdapter的getExistingOrCreate()繼續處理
// 源碼位置:org.springframework.boot.autoconfigure.validation.ValidatorAdapter
private static Validator getExistingOrCreate(ApplicationContext applicationContext) {// 6. 先獲取已經存在的ValidatorValidator existing = getExisting(applicationContext);if (existing != null) {return wrap(existing, true);}// 8. 沒有存在的就創建一個return create(applicationContext);
}
private static Validator create(MessageSource messageSource) {// 9. 直接創建一個OptionalValidatorFactoryBean,它是Validator的一個工廠bean//    繼承關系:OptionalValidatorFactoryBean < LocalValidatorFactoryBean < SpringValidatorAdapter < javax.validation.ValidatorOptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean();try {MessageInterpolatorFactory factory = new MessageInterpolatorFactory(messageSource);validator.setMessageInterpolator(factory.getObject());}catch (ValidationException ex) {}return wrap(validator, false);
}// 10. OptionalValidatorFactoryBean的父類SpringValidatorAdapter里有個關鍵的對象targetValidator,
//     實際的validate工作都是由它完成的,但上面OptionalValidatorFactoryBean()創建的時候使用的是無參構造,
//     則SpringValidatorAdapter里面的targetValidator也沒有賦值,所以還做不了實際的validate工作
// 源碼位置:org.springframework.validation.beanvalidation.SpringValidatorAdapter
public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {private javax.validation.Validator targetValidator;public SpringValidatorAdapter(javax.validation.Validator targetValidator) {Assert.notNull(targetValidator, "Target Validator must not be null");this.targetValidator = targetValidator;}SpringValidatorAdapter() {}void setTargetValidator(javax.validation.Validator targetValidator) {this.targetValidator = targetValidator;}public void validate(Object target, Errors errors) {// 由targetValidator進行實際的validate工作if (this.targetValidator != null) {processConstraintViolations(this.targetValidator.validate(target), errors);}}// 省略其它代碼
}// 回到ValidatorAdapter的create()繼續處理
// 源碼位置:org.springframework.boot.autoconfigure.validation.ValidatorAdapter
private static Validator create(MessageSource messageSource) {// 9. 直接創建一個OptionalValidatorFactoryBean,它是Validator的一個工廠beanOptionalValidatorFactoryBean validator = new OptionalValidatorFactoryBean();try {MessageInterpolatorFactory factory = new MessageInterpolatorFactory(messageSource);validator.setMessageInterpolator(factory.getObject());}catch (ValidationException ex) {}// 11. 把Validator包裝到ValidatorAdapter返回return wrap(validator, false);
}// 源碼位置:org.springframework.boot.autoconfigure.validation.ValidatorAdapter
private static Validator wrap(Validator validator, boolean existingBean) {if (validator instanceof javax.validation.Validator) {// 12. 從上面繼承關系可以看到OptionalValidatorFactoryBean是SpringValidatorAdapter子類,if條件成立if (validator instanceof SpringValidatorAdapter) {return new ValidatorAdapter((SpringValidatorAdapter) validator, existingBean);}// 如果自定義提供一個實現了Validator接口且不是SpringValidatorAdapter子類的validator,則封裝到SpringValidatorAdapter里return new ValidatorAdapter(new SpringValidatorAdapter((javax.validation.Validator) validator),existingBean);}// 使用WebMvcConfigurer提供的屬于org.springframework.validation.Validator類型,不包裝到SpringValidatorAdapter而直接作為mvcValidatorreturn validator;
}// 回到EnableWebMvcConfiguration的mvcValidator()
// 從這個過程看,目前只是創建了OptionalValidatorFactoryBean(包裝到了ValidatorAdapter里),還沒有真正創建Validator,
// 這個ValidatorAdapter成為注入到Spring容器的bean,名稱是mvcValidator
// 源碼位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
@Bean
public Validator mvcValidator() {if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {return super.mvcValidator();}// 1. 調getValidator()獲取Validator// 4. getValidator()獲取的Validator作為默認的,繼續由ValidatorAdapter.get()處理// 13. 完成mvcValidator創建,mvcValidator實際是一個包裝了OptionalValidatorFactoryBean的ValidatorAdapter//     OptionalValidatorFactoryBean的父類LocalValidatorFactoryBean實現了InitializingBean接口會觸發afterPropertiesSet()調用return ValidatorAdapter.get(getApplicationContext(), getValidator());
}// 源碼位置:org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
public void afterPropertiesSet() {Configuration<?> configuration;// 14. 如果提供了providerClass,則由providerClass來進行配置,否則加載classpath下services的ValidationProvider//     由于上面OptionalValidatorFactoryBean是直接new出來的,這個providerClass也沒有賦值if (this.providerClass != null) {ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass);if (this.validationProviderResolver != null) {bootstrap = bootstrap.providerResolver(this.validationProviderResolver);}configuration = bootstrap.configure();}else {GenericBootstrap bootstrap = Validation.byDefaultProvider();if (this.validationProviderResolver != null) {bootstrap = bootstrap.providerResolver(this.validationProviderResolver);}// 這里會加載classpath下services的ValidationProvider,如果引了hibernate-validator包就會有,// 如果沒有引hibernate-validator包,這里會拋異常結束,即沒有創建實際的Validatorconfiguration = bootstrap.configure();}// 省略部分代碼try {// 實際的Validator要由validationProvider提供this.validatorFactory = configuration.buildValidatorFactory();setTargetValidator(this.validatorFactory.getValidator());}finally {closeMappingStreams(mappingStreams);}
}
從上面代碼看,如果希望mvcValidator里的targetValidator有值,有兩個方法:
  • 用WebMvcConfigurer提供一個實現了Validator接口且不是SpringValidatorAdapter子類的validator。
  • 通過Services的方式提供。

2.2 設置mvcValidator

RequestMappingHandlerAdapter是SpringMVC處理http請求的關鍵類,在初始化的時候設置了WebBindingInitializer,而mvcValidator則設置到了WebBindingInitializer里,通過這種方式把mvcValidator組裝到了RequestMappingHandlerAdapter里。
// 源碼位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {// 1. 初始化RequestMappingHandlerAdapter時,把mvcValidator設置到RequestMappingHandlerAdapterRequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator);adapter.setIgnoreDefaultModelOnRedirect(this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());return adapter;
}// 源碼位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
// 繼承關系: EnableWebMvcConfiguration < DelegatingWebMvcConfiguration < WebMvcConfigurationSupport
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();adapter.setContentNegotiationManager(contentNegotiationManager);adapter.setMessageConverters(getMessageConverters());// 2. mvcValidator實際是設置到了WebBindingInitializer,而WebBindingInitializer則設置到了RequestMappingHandlerAdapter里adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));adapter.setCustomArgumentResolvers(getArgumentResolvers());adapter.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}AsyncSupportConfigurer configurer = getAsyncSupportConfigurer();if (configurer.getTaskExecutor() != null) {adapter.setTaskExecutor(configurer.getTaskExecutor());}if (configurer.getTimeout() != null) {adapter.setAsyncRequestTimeout(configurer.getTimeout());}adapter.setCallableInterceptors(configurer.getCallableInterceptors());adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());return adapter;
}// 源碼位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {try {return this.beanFactory.getBean(ConfigurableWebBindingInitializer.class);}catch (NoSuchBeanDefinitionException ex) {// 3. 在父類設置return super.getConfigurableWebBindingInitializer(mvcConversionService, mvcValidator);}
}// 源碼位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
protected ConfigurableWebBindingInitializer getConfigurableWebBindingInitializer(FormattingConversionService mvcConversionService, Validator mvcValidator) {// 4. 創建WebBindingInitializer(ConfigurableWebBindingInitializer),并設置mvcValidatorConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();initializer.setConversionService(mvcConversionService);initializer.setValidator(mvcValidator);MessageCodesResolver messageCodesResolver = getMessageCodesResolver();if (messageCodesResolver != null) {initializer.setMessageCodesResolver(messageCodesResolver);}return initializer;
}

2.3 請求參數校驗

當發起HTTP請求的時候,SpringMVC會通過RequestMappingHandlerAdapter把mvcValidator設置到DataBinder(ExtendedServletRequestDataBinder)中,在對請求參數處理的時候,如果參數指定了需要校驗的注解(如@Valid),則在DataBinder里用Validator對參數進行校驗,校驗的結果放到BindingResult里。最后判斷BindingResult里是否帶了錯誤信息,如果帶了則拋異常,如果拋異常則不會進入Controller的接口。

// BindingResult接口是Errors接口的子接口,它們是Spring的Validation機制的重要概念。
// Errors主要用于存儲對象和對象方法的validation錯誤,前者用reject()接口,后者用rejectValue()接口。
// BindingResult則還記錄了產生validation錯誤的對象
public interface BindingResult extends Errors {Object getTarget();// 省略其它接口
}
// 源碼位置:org.springframework.validation.Errors
public interface Errors {void reject(String errorCode);void reject(String errorCode, String defaultMessage);void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);void rejectValue(@Nullable String field, String errorCode);void rejectValue(@Nullable String field, String errorCode, String defaultMessage);void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);// 省略其它接口
}// RequestMappingHandlerAdapter處理http請求時,在匹配請求參數和Controller接口參數的時候,需要用到各種各樣的MethodProcessor,
// 當參數類型屬于自定義的類型,匹配到的處理類是ModelAttributeMethodProcessor;
// binderFactory為ServletRequestDataBinderFactory,里面帶了ConfigurableWebBindingInitializer,WebBindingInitializer帶了mvcValidator;
// binderFactory的ConfigurableWebBindingInitializer由RequestMappingHandlerAdapter提供。
// 源碼位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {String name = ModelFactory.getNameForParameter(parameter);ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;if (mavContainer.containsAttribute(name)) {attribute = mavContainer.getModel().get(name);}else {try {// 1. 創建參數類型對應的對象,如例子里的GroupMemberattribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {throw ex;}if (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();}else {attribute = ex.getTarget();}bindingResult = ex.getBindingResult();}}if (bindingResult == null) {// 2. 創建一個WebDataBinder,由父類DefaultDataBinderFactory提供createBinder()方法WebDataBinder binder = binderFactory.createBinder()方法(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {bindRequestParameters(binder, webRequest);}validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}if (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;
}// 源碼位置:org.springframework.web.bind.support.DefaultDataBinderFactory
public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {// 3. 調用ServletRequestDataBinderFactory的createBinderInstance()創建//    繼承關系:ServletRequestDataBinderFactory < InitBinderDataBinderFactory < DefaultDataBinderFactoryWebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);if (this.initializer != null) {this.initializer.initBinder(dataBinder, webRequest);}initBinder(dataBinder, webRequest);return dataBinder;
}// 源碼位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory
protected ServletRequestDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest request) throws Exception  {// 4. 創建WebDataBinder對象return new ExtendedServletRequestDataBinder(target, objectName);
}// 回到DefaultDataBinderFactory的createBinder()繼續處理
// 源碼位置:org.springframework.web.bind.support.DefaultDataBinderFactory
public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {// 3. 調用ServletRequestDataBinderFactory的createBinderInstance()創建ExtendedServletRequestDataBinderWebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);if (this.initializer != null) {// 4. 初始化WebDataBinder//    initializer為ConfigurableWebBindingInitializer,實現了WebBindingInitializer接口,initBinder()由接口提供this.initializer.initBinder(dataBinder, webRequest);}initBinder(dataBinder, webRequest);return dataBinder;
}// 源碼位置:org.springframework.web.bind.support.WebBindingInitializer
default void initBinder(WebDataBinder binder, WebRequest request) {// 5. 調用子類方法初始化,子類為ConfigurableWebBindingInitializerinitBinder(binder);
}// 源碼位置:org.springframework.web.bind.support.ConfigurableWebBindingInitializer
public void initBinder(WebDataBinder binder) {binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);if (this.directFieldAccess) {binder.initDirectFieldAccess();}if (this.messageCodesResolver != null) {binder.setMessageCodesResolver(this.messageCodesResolver);}if (this.bindingErrorProcessor != null) {binder.setBindingErrorProcessor(this.bindingErrorProcessor);}// 6. 把mvcValidator設置到WebDataBinder中,mvcValidator實際為SpringValidatorAdapterif (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {binder.setValidator(this.validator);}if (this.conversionService != null) {binder.setConversionService(this.conversionService);}if (this.propertyEditorRegistrars != null) {for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {propertyEditorRegistrar.registerCustomEditors(binder);}}
}// 回到ModelAttributeMethodProcessor繼續處理
// 源碼位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 省略部分代碼if (bindingResult == null) {// 2. 創建一個WebDataBinder,由父類DefaultDataBinderFactory提供createBinder()方法WebDataBinder binder = binderFactory.createBinder()方法(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {bindRequestParameters(binder, webRequest);}// 7. 校驗參數值validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}if (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;
}// 源碼位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {for (Annotation ann : parameter.getParameterAnnotations()) {// 8. 把參數前指定的注解取出來,確定哪些是和validation有關的Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);if (validationHints != null) {binder.validate(validationHints);break;}}
}// 9. 一般要校驗的參數前面要加上@Valid注解
//    從下面代碼看不止這一種方式,還可以指定有值的@Validated注解,也可以自定義以Valid開頭的注解等
// 源碼位置:org.springframework.validation.annotation.ValidationAnnotationUtils
public static Object[] determineValidationHints(Annotation ann) {// 指定了注解@Validatedif (ann instanceof Validated) {return ((Validated) ann).value();}// 指定了注解@ValidClass<? extends Annotation> annotationType = ann.annotationType();if ("javax.validation.Valid".equals(annotationType.getName())) {return EMPTY_OBJECT_ARRAY;}// 注解間接指定了注解@ValidatedValidated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);if (validatedAnn != null) {return validatedAnn.value();}// 自定義注解是以Valid開頭命名的if (annotationType.getSimpleName().startsWith("Valid")) {return convertValidationHints(AnnotationUtils.getValue(ann));}return null;
}// 回到ModelAttributeMethodProcessor的validateIfApplicable()繼續處理
// 源碼位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {for (Annotation ann : parameter.getParameterAnnotations()) {// 8. 把參數前指定的注解取出來,確定哪些是和validation有關的Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);if (validationHints != null) {// 如果匹配到了注解,則進行校驗binder.validate(validationHints);break;}}
}// 源碼位置:org.springframework.validation.DataBinder
public void validate(Object... validationHints) {Object target = getTarget();// 記錄校驗錯誤的BindingResult,一般為BeanPropertyBindingResultBindingResult bindingResult = getBindingResult();// 9. 獲取到所有的Validator來校驗,從前面看基本只有一個for (Validator validator : getValidators()) {if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {((SmartValidator) validator).validate(target, bindingResult, validationHints);}else if (validator != null) {// 10. 調用Validator的validate()方法進行校驗,校驗的結果要放到bindingResult中validator.validate(target, bindingResult);}}
}// 源碼位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 省略部分代碼if (bindingResult == null) {// 2. 創建一個WebDataBinder,由父類DefaultDataBinderFactory提供createBinder()方法WebDataBinder binder = binderFactory.createBinder()方法(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {bindRequestParameters(binder, webRequest);}// 7. 校驗參數值validateIfApplicable(binder, parameter);// 11. 如果有validation錯誤就拋異常if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}if (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;
}

2.4 Validation的使用

Spring提供的Validation模型為:
模型的核心部分是:
  • Valid和Validated注解:主要是用來識別是否需要進行校驗,如果不加這些注解,那么就相當于沒有開啟校驗。注解由參數MethodParameter提供。
  • Validator:主要實現校驗邏輯。supports()用于識別參數的類型,validate()則完成具體的校驗邏輯。
  • Errors:用于存儲校驗的錯誤結果,分為整個對象的錯誤(如對象為null)和屬性錯誤。ModelAttributeMethodProcessor等會從它里面獲取錯誤進行處理。
  • WebDataBinder用于組合Validator和Errors,完成整體的校驗。
從上面看,要使用Spring提供的Validation對參數校驗需要:
1) 自定義一個校驗類,實現org.springframework.validation.Validator接口:
public interface Validator {boolean supports(Class<?> clazz);void validate(Object target, Errors errors);
}

2) 把這個自定義類加到WebMvcConfigurer中

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic Validator getValidator() {return new CustomValidator();}
}

3) 在Validator的validate()校驗到錯誤的時候,錯誤信息要加到Errors中

在support()里要定義好HTTP請求參數類型的匹配規則,這個方法的參數是HTTP請求參數對應的類。最直接的方式就是列出所有要支持校驗的類,但這樣擴展性就比較差,如果要擴展成一個框架,就得用其它方式,比如定義一些注解,通過這個類型來獲取是否指定了注解,然后根據注解類做事情。

同理,validate()工作如果不是硬編碼也不太容易。所以Spring提供的Validation有點比較原始,不方便直接使用。

3 架構一小步

使用Spring提供的@Valid注解標識Controller接口里接收的參數,增加擴展來支持校驗(這個擴展有現成的hibernate-validator包)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/90601.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/90601.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/90601.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

0編程基礎:用TRAE寫出了會蹦跳躲避散發炫光的貪吃蛇小游戲

在某個深夜的代碼深淵里&#xff0c;一個從未寫過print("Hello World")的小白開發者&#xff0c;竟用自然語言指令讓貪吃蛇跳起了"光棱華爾茲"——蛇身折射出彩虹軌跡&#xff0c;食物像星艦般自動規避追擊&#xff0c;甚至實現了四頭蛇的"量子糾纏式…

在Word和WPS文字中要同時查看和編輯一個文檔的兩個地方?拆分窗口

如果要在Word或WPS文字的長文檔中同時查看兩部同步的地方&#xff08;文檔位置&#xff09;&#xff0c;來回跳轉和滾動費時費力&#xff0c;使用拆分窗口的功能即可搞定。將窗口一分為二&#xff0c;上下對照非常方便。一、拆分窗口的路徑Word和WPS基本一樣&#xff0c;就是菜…

Windows系統下安裝mujoco環境的教程【原創】

在學習Mujoco仿真的過程中&#xff0c;我先前是在linux系統下進行的研究與學習&#xff0c;今天來試試看在windows系統中安裝mujoco仿真環境。 先前在linux中的一些關于mujoco學習記錄的博客&#xff1a;Mujoco仿真【xml文件的學習 3】_mujoco打開xml文件-CSDN博客 下面開始wi…

CSS中篇

#Flex布局#1、什么是flex布局&#xff1f;flex 布局&#xff0c;全稱彈性布局&#xff08;Flexible Box Layout&#xff09;&#xff0c;是 CSS3 中引入的一種新的布局模式。它主要通過給容器設置相關屬性&#xff0c;來控制容器內部子元素的排列方式。相比傳統的浮動布局和定位…

《云計算藍皮書 2025 》發布:云計算加速成為智能時代核心引擎

近日&#xff0c;中國信息通信研究院發布了《云計算藍皮書&#xff08;2025 年&#xff09;》&#xff0c;全面剖析了云計算領域的發展現狀與未來趨勢。在人工智能蓬勃發展的當下&#xff0c;云計算正從基礎資源供給向智能時代的核心引擎加速轉變&#xff0c;成為重塑全球數字競…

excel刪除重復項場景

問題描述 問題描述&#xff1a;因為表格中存在多條相同的數據&#xff0c;我現在excel有一列&#xff0c;值為#N/A 。另外有列叫做藥品名稱、規格、廠家 我要刪除值為 #N/A&#xff0c;并且 藥品名稱、規格、廠家相等的數據&#xff0c;那條相同的刪掉,只保留一條&#xff0c;…

Vue 3 與 Element Plus 中的 /deep/ 選擇器問題

Vue 3 與 Element Plus 中的 /deep/ 選擇器問題 在 Vue3 中使用 Element Plus 組件時&#xff0c;使用 ::v-deep或 :deep()的場景取決于 ??樣式作用域?? 和 ??選擇器目標??。以下是關鍵區別&#xff1a;

2025暑期—06神經網絡-常見網絡

六個濾波核提取特征Maps5X5 卷積核&#xff0c;1個閾值 6個元素&#xff0c;卷積后兩邊各少兩個&#xff0c;28*28像素 又有6個卷積核&#xff0c;所以有122304個連接&#xff0c;連接數不多是因為很多都是公用參數的。池化是參數池化&#xff0c;和當前平均最大不一樣。編程14…

硅基計劃3.0 學習總結 叁 棧和隊列

文章目錄一、棧1. 模擬實現棧2. 小試牛刀1. 判斷一個棧的出棧順序是否為題目給定情況2. 括號匹配3. 逆波蘭表達式求值4. 求最小棧元素3. 單鏈表實現棧二、隊列1. 官方隊列類Queue2. 雙向鏈表模擬實現Queue類3. 順序表模擬實現Queue類4. 雙端隊列5. 隊列實現棧6. 棧實現隊列一、…

飛行控制領軍者 | 邊界智控攜高安全級飛控系統亮相2025深圳eVTOL展

2025深圳eVTOL展將于2025年9月23日至25日在深圳坪山燕子湖國際會展中心盛大舉辦。本屆展會以 “低空經濟?eVTOL?航空應急救援?商載大型無人運輸機” 為核心&#xff0c;預計將匯聚200余位發言嘉賓、500 余家頂尖展商及15,000余名專業觀眾&#xff0c;規模盛大&#xff0c;精…

React狀態管理——Dva

目錄 一、安裝依賴 二、Dva注冊model方式 2.1 自動注冊models 2.2 手動注冊model方式 三、創建 dva 實例 四、創建 model 五、在組件中使用 六、動態加載Dva Model Dva 是一個基于 redux 和 redux-saga 的輕量級前端框架&#xff0c;可以方便地在 React 應用中管理狀態…

編程與數學 03-002 計算機網絡 05_以太網技術

編程與數學 03-002 計算機網絡 05_以太網技術一、以太網的基本原理&#xff08;一&#xff09;CSMA/CD協議的工作原理&#xff08;二&#xff09;以太網的幀結構二、以太網的拓撲結構與設備&#xff08;一&#xff09;傳統以太網的拓撲結構&#xff08;二&#xff09;交換機的工…

解決英文版Windows10安裝WireShark報錯

問題點擊WireShark安裝包進行安裝時報錯原因編碼方式故障解決方式修改操作系統編碼1.WinR&#xff0c;輸入Control&#xff0c;打開控制面板2.點擊Small icons3.點擊Region4.設置編碼UTF-8

利用aruco標定板標定相機

1、生成aruco標定板#include <opencv2/opencv.hpp> #include <opencv2/aruco.hpp> #include <opencv2/objdetect/aruco_detector.hpp> #include <iostream> #include <string>using namespace cv; using namespace std;int main() {int markers…

C/C++語言程序使用三種主要的內存分配方式,和python語言一樣么?

這是一個很好的比較問題&#xff01;C/C 和 Python 在內存分配方式上有本質的區別&#xff0c;雖然它們最終使用的都是計算機的物理內存&#xff08;堆、棧等&#xff09;&#xff0c;但語言層面提供的抽象和管理機制完全不同。核心區別&#xff1a;控制權&#xff1a; C/C 程序…

小電流驅動大電流:原理、實現方式與應用前景

目錄 一、什么是“小電流驅動大電流”&#xff1f; 舉個例子&#xff1a; 二、核心原理與實現方式 1. 電流放大原理 2. 電子開關元件 3. 控制電路設計 4. 附加保護措施 三、為什么采用“小電流驅動大電流”&#xff1f; 1. 提高安全性 2. 降低能耗 3. 改善效率 4. …

【DM數據守護集群搭建-讀寫分離】

DM數據守護集群搭建-讀寫分離 讀寫分離集群由一個主庫以及一個或者多個配置了即時&#xff08;Timely&#xff09;歸檔或實時&#xff08;Realtime&#xff09;歸檔的備庫組成&#xff0c;其主要目標是在保障數據庫可用性基礎上&#xff0c;實現讀、寫操作的自動分離&#xff0…

earth靶場

1、找ip和端口主機是192.168.6.213&#xff0c;因此靶場ip就是192.168.6.34&#xff0c;三個端口開放&#xff0c;我們去訪問一下頁面。三個端口都無法訪問。我們使用nmap進行dns解析。nmap -A -p- -T4 -sV 192.168.6.34把這兩條解析添加到hosts文件中去&#xff0c;這樣我們才…

Kafka——Java消費者是如何管理TCP連接的?

引言在分布式消息系統中&#xff0c;網絡連接是數據流轉的"血管"&#xff0c;其管理效率直接決定了系統的吞吐量、延遲與穩定性。作為Kafka生態中負責數據消費的核心組件&#xff0c;Java消費者&#xff08;KafkaConsumer&#xff09;的TCP連接管理機制一直是開發者理…

idea監控本地堆棧

idea 安裝插件 VisualVM Launcher重啟idea后&#xff0c;配置 VisualVM 屬性選擇自己jdk的 jvisualvm啟動時&#xff0c;選擇監控&#xff0c;會自動彈出 VisualVM