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