• 【SpringMVC】springmvc中的数据校验


    在开始本篇分析之前,先来看两篇文章:
    1,基础理论知识:求求你别在用 IF ELSE 校验参数了
    2,演示示例:SpringMVC/Boot中的校验框架 @Valid 和 @Validated的使用

    这里注意:springmvc中只是引用了校验框架,真正的校验功能,springmvc没有实现。真正干事的是校验框架,所以本篇是分析,springmvc是如何使用校验框架的。

    在执行处理器方法之前,一定是要解析出方法的参数值。不同的参数由不同的参数解析器负责。什么样的才会用到校验呢?答案:javaBean类型的;涉及到了解析复杂参数的ModelAttributeMethodProcessor,和用来处理@requestBody的RequestResponseBodyMethodProcessor。


    RequestResponseBodyMethodProcessor

    	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    		parameter = parameter.nestedIfOptional();
    		// 解析出值
    		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    		String name = Conventions.getVariableNameForParameter(parameter);
    
    		if (binderFactory != null) {
    			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    			if (arg != null) {
    				// 进行校验
    				validateIfApplicable(binder, parameter);
    				// 如果校验有错误,并且这个参数的后面没有BindingResult类型的,那么就抛异常。
    				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
    				}
    			}
    			// 把这个绑定结果方法哦model中,处理器BindingResult类型的参数解析器得到值。
    			if (mavContainer != null) {
    				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    			}
    		}
    
    		return adaptArgumentIfNecessary(arg, parameter);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    校验解析出来的参数,如果这个参数后面跟的是BindingResult,那么特定的参数解析器会将绑定结果解出来;如果没有跟,那么如果校验出错误,抛异常MethodArgumentNotValidException。

    这一步的作用就是将绑定结果放到model中,等待ErrorsMethodArgumentResolver处理器解析出参数。

    if (mavContainer != null) {
    				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    			}
    
    // org.springframework.web.method.annotation.ErrorsMethodArgumentResolver#resolveArgument
    	public Object resolveArgument(MethodParameter parameter,
    			@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
    			@Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    		Assert.state(mavContainer != null,
    				"Errors/BindingResult argument only supported on regular handler methods");
    
    		ModelMap model = mavContainer.getModel();
    		String lastKey = CollectionUtils.lastElement(model.keySet());
    		if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
    			return model.get(lastKey);
    		}
    
    		throw new IllegalStateException(
    				"An Errors/BindingResult argument is expected to be declared immediately after " +
    				"the model attribute, the @RequestBody or the @RequestPart arguments " +
    				"to which they apply: " + parameter.getMethod());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    下面看下是如何校验的;
    org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#validateIfApplicable

    	protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    		Annotation[] annotations = parameter.getParameterAnnotations();
    		// 遍历这个参数的注解。
    		for (Annotation ann : annotations) {
    			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
    			// 如果有Validated注解,或者注解的名称是Valid开头,说明需要校验。
    			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
    				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
    				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
    				binder.validate(validationHints);
    				break;
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    	public void validate(Object... validationHints) {
    		// 得到目标值
    		Object target = getTarget();
    		Assert.state(target != null, "No target to validate");
    		BindingResult bindingResult = getBindingResult();
    		// 得到所有的校验工具。进行校验。结果是放在bindingResult中。
    		for (Validator validator : getValidators()) {
    			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
    				((SmartValidator) validator).validate(target, bindingResult, validationHints);
    			}
    			else if (validator != null) {
    				validator.validate(target, bindingResult);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ModelAttributeMethodProcessor#resolveArgument

    		。。。。
    
    		if (bindingResult == null) {
    			// Bean property binding and validation;
    			// skipped in case of binding failure on construction.
    			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    			if (binder.getTarget() != null) {
    				if (!mavContainer.isBindingDisabled(name)) {
    					bindRequestParameters(binder, webRequest);
    				}
    				// 校验返回值
    				validateIfApplicable(binder, parameter);
    				// 这里抛异常是BindException
    				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    					throw new BindException(binder.getBindingResult());
    				}
    			}
    			// Value type adaptation, also covering java.util.Optional
    			if (!parameter.getParameterType().isInstance(attribute)) {
    				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    			}
    			bindingResult = binder.getBindingResult();
    		}
    
    		// Add resolved attribute and BindingResult at the end of the model
    		Map<String, Object> bindingResultModel = bindingResult.getModel();
    		mavContainer.removeAttributes(bindingResultModel);
    		mavContainer.addAllAttributes(bindingResultModel);
    
    		return attribute;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    到目前为止校验的都是javaBean类型的。处理javabean,参数还有很多类型呀:integer类型,我想控制这个值在在某个范围;String类型,我想校验值的长度,等等;但这些普通类型的参数解析器不会校验,直接返回值。spring提供了另一种方式解决校验方法的功能。使用代理的方式,在参数都解析好了之后,我先校验下参数,之后再执行处理器方法。springmvc是如何代理的呢?

    对bean的代理,就是操作bean喽,那么一定是bean处理器。看下这个bean处理器:

    可以看到是springaop的逻辑。AbstractBeanFactoryAwareAdvisingPostProcessor

    public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
    
    • 1

    看这个方法,定义了起点和增强处理方法。

    	public void afterPropertiesSet() {
    		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
    		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    	}
    
    • 1
    • 2
    • 3
    • 4

    切点很好理解,就是对类的过滤,根据提供的条件。之后再对类中的方法进行过滤;
    增强方法是处理的逻辑方法。

    切点:对那些类进行过滤呢?

    	public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {
    		// 对类的过滤方法
    		this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
    		// 对方法过滤的方法,可以看到,这个逻辑是对类的额所有方法都应用。
    		this.methodMatcher = MethodMatcher.TRUE;
    	}
    
    	// 看类的匹配方法,类的上面要有Validated注解。
    	public boolean matches(Class<?> clazz) {
    		return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :
    				clazz.isAnnotationPresent(this.annotationType));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ok,过滤的条件就是标注Validated的类的所有方法。目标找到了,找到后做什么事情呢?这是增强的逻辑了。

    	public void afterPropertiesSet() {
    		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
    		// 切面,createMethodValidationAdvice返回的是增强类
    		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    	}
    	// MethodValidationInterceptor是增强类
    	protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
    		return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
    	}
    
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    	public Object invoke(MethodInvocation invocation) throws Throwable {
    		// FactoryBean.getObject类型的放不用代理
    		if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
    			return invocation.proceed();
    		}
    
    		Class<?>[] groups = determineValidationGroups(invocation);
    
    		// Standard Bean Validation 1.1 API
    		ExecutableValidator execVal = this.validator.forExecutables();
    		Method methodToValidate = invocation.getMethod();
    		Set<ConstraintViolation<Object>> result;
    		
    		// 开始校验参数
    		try {
    			result = execVal.validateParameters(
    					invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
    		}
    		catch (IllegalArgumentException ex) {
    			// 如果有异常,校验这个方法对应的桥接方法
    			methodToValidate = BridgeMethodResolver.findBridgedMethod(
    					ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
    			result = execVal.validateParameters(
    					invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
    		}
    		// 如果结果不是空,说明有问题,直接抛异常;
    		if (!result.isEmpty()) {
    			throw new ConstraintViolationException(result);
    		}
    		
    		// 没有异常,执行处理器方法,也就是Controller的方法
    		Object returnValue = invocation.proceed();
    		
    		// 校验返回值。
    		result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
    		if (!result.isEmpty()) {
    			throw new ConstraintViolationException(result);
    		}
    
    		return returnValue;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    可以看到使用校验框架进行校验。方法的参数校验,返回值也要校验。如果有错误。抛异常:ConstraintViolationException。

    在强调一遍,springmvc的校验是基础校验组件的。自己并没有实现;所以可以用不同的校验组件,我们属性的就是Hibernate Validator ,如果想换校验组件;注入bean就可以。

    还有最上面的文章标题是springmvc,springboot,这明显是错误的,springboot前端用的就是springmvc,他自己没有校验。springboot用的东西都用spring的,他只是做了自动装配。

  • 相关阅读:
    实现两数交换
    关于机器学习SVM中KKT条件的深入理解推导
    Springboot集成Mybatisplus,轻松CRUD
    现在完成时习题
    实验十四:雨滴传感器实验
    Python学习笔记9:入门知识(九)
    CentOS7安装部署Nacos
    嵌入式技术面试基本规则
    Vue:生命周期
    C#,怎么修改(VS)Visual Studio 2022支持的C#版本
  • 原文地址:https://blog.csdn.net/qq_34501351/article/details/126179278