Spring Context : MethodValidationInterceptor

本文深入探讨了Spring框架中MethodValidationInterceptor的实现细节及其在JSR-303验证中的应用。介绍了如何使用该拦截器对方法参数及返回值进行验证,并解释了其与Spring注解@Validated的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

MethodValidationInterceptorSpring Context提供的一个MethodInterceptor实现,它使用一个指定的JSR-303验证器对使用了相应JSR-303验证注解的方法参数或者返回值做验证。例子 :

// 注意,同时所属类上要使用 Spring 注解 @Validated
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)

该例子约定如下验证逻辑 :

  1. 参数arg1必须不能为null;
  2. 参数arg2最大值为10;
  3. 方法myValidMethod返回值不能是null;

另外,类级别使用的Spring注解@Validated可以定义验证组,该验证注解会应用到该类所有public方法上。缺省情况下,JSR-303会仅仅使用缺省验证组进行验证。

Spring 5.0开始,MethodValidationInterceptor需要的验证器必须是Bean Validation 1.1版本。

关于MethodValidationInterceptor的应用,可以参考:Spring BeanPostProcessor : MethodValidationPostProcessor,这篇文章中,MethodValidationPostProcessor会引入MethodValidationInterceptor。而进一步关于MethodValidationPostProcessor和所使用到的Validator,可以参考:Spring Boot 自动配置 : ValidationAutoConfiguration,对于一个Spring Boot应用,它会通过ValidationAutoConfiguration定义两个bean:一个是Validator,一个是MethodValidationPostProcessor

源代码

源代码版本 Spring Context 5.1.5.RELEASE

package org.springframework.validation.beanvalidation;

// 省略 import 行


public class MethodValidationInterceptor implements MethodInterceptor {

    // 要使用的 JSR-303 验证器,需要通过构造函数被外部指定
	private final Validator validator;


	/**
	 * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
	 */
	public MethodValidationInterceptor() {
		this(Validation.buildDefaultValidatorFactory());
	}

	/**
	 * Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory.
	 * @param validatorFactory the JSR-303 ValidatorFactory to use
	 */
	public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
		this(validatorFactory.getValidator());
	}

	/**
	 * Create a new MethodValidationInterceptor using the given JSR-303 Validator.
	 * @param validator the JSR-303 Validator to use
	 */
	public MethodValidationInterceptor(Validator validator) {
		this.validator = validator;
	}


    // MethodInterceptor 接口约定的方法 , 逻辑实现主流程如下 :
    // 1. 执行方法参数验证逻辑
    // 2. 调用目标方法
    // 3. 执行方法返回值验证逻辑
	@Override
	@SuppressWarnings("unchecked")
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
		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;

       // 方法参数上的验证逻辑,验证结果保存在 result 中,如果验证失败, result 不为空,
       // 此时会抛出异常 ConstraintViolationException
		try {
			result = execVal.validateParameters(
					invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
		}
		catch (IllegalArgumentException ex) {
			// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
			// Let's try to find the bridged method on the implementation class...
			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);
		}

       // 调用目标方法 
		Object returnValue = invocation.proceed();

       // 对方法执行的返回值进行验证 ,验证结果保存在 result 中,如果验证失败, result 不为空,
       // 此时会抛出异常 ConstraintViolationException
		result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

       // 验证逻辑和目标方法执行都正常,这里返回目标方法的执行结果 
		return returnValue;
	}

	private boolean isFactoryBeanMetadataMethod(Method method) {
		Class<?> clazz = method.getDeclaringClass();

		// Call from interface-based proxy handle, allowing for an efficient check?
		if (clazz.isInterface()) {
			return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
					!method.getName().equals("getObject"));
		}

		// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
		Class<?> factoryBeanType = null;
		if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = SmartFactoryBean.class;
		}
		else if (FactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = FactoryBean.class;
		}
		return (factoryBeanType != null && !method.getName().equals("getObject") &&
				ClassUtils.hasMethod(factoryBeanType, method.getName(), method.getParameterTypes()));
	}

	/**
	 * Determine the validation groups to validate against for the given method invocation.
	 * <p>Default are the validation groups as specified in the {@link Validated} annotation
	 * on the containing target class of the method.
	 * @param invocation the current MethodInvocation
	 * @return the applicable validation groups as a Class array
	 */
	protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
		Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
		if (validatedAnn == null) {
			validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
		}
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}

}

参考资料

### Spring 项目中业务对象属性值验证设计方案 为了确保业务对象属性值的有效性和一致性,在Spring框架下可以采用多种方式来实现不同逻辑的校验,从而保障系统的健壮性和可维护性。 #### 使用JSR-303/Bean Validation标准进行基础校验 通过引入Hibernate Validator库,能够快速地为实体类字段添加基本约束条件。这些内置注解涵盖了大多数常见的数据有效性检查需求,如`@NotNull`, `@Size`, `@Email`等[^1]。 ```java public class User { @NotBlank(message = "用户名不能为空") private String username; @Pattern(regexp="^[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+(?:\\.[a-zA-Z0-9_!#$%&’*+/=?`{|}~^-]+)*@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$", message="邮箱格式不正确") private String email; // getters and setters omitted for brevity } ``` #### 自定义Validator实现复杂场景下的校验规则 当遇到较为复杂的业务逻辑无法仅靠简单的注解完成时,则可以通过创建自定义的`ConstraintValidator`接口实例来进行更灵活多变的数据检验工作。这种方式允许开发者编写任意程度上定制化的验证算法,并将其无缝集成到现有的表单提交流程之中[^2]。 ```java @Documented @Target({ METHOD, FIELD }) @Retention(RUNTIME) @Constraint(validatedBy = PasswordStrengthValidator.class) public @interface ValidPassword { String message() default "密码强度不足"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } public class PasswordStrengthValidator implements ConstraintValidator<ValidPassword, String> { public boolean isValid(String password, ConstraintValidatorContext context) { // Implement complex logic here to validate the strength of a given password. return true; // Placeholder implementation } } ``` #### 利用MethodValidationInterceptor增强方法级参数校验能力 除了针对POJO类型的成员变量外,还可以借助于`MethodValidationPostProcessor`组件配合AOP切面编程技术实现在服务层接口调用前自动触发的方法入参合法性审查机制。这不仅提高了代码重用率同时也简化了控制器层面异常处理过程中的繁杂操作. ```xml <!-- applicationContext.xml --> <mvc:annotation-driven validator="validator"/> <bean id="methodValidationPostProcessor" class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/> <!-- Enable AspectJ support --> <aop:aspectj-autoproxy/> ``` #### 集成第三方库拓展更多高级特性支持 对于某些特定领域内的特殊要求(比如地理位置坐标范围限定),可以选择接入成熟的开源工具包作为辅助手段进一步强化应用的安全防护水平。例如GeoTools就是一个不错的选择它提供了丰富的地理空间运算API可供选用. 综上所述,上述几种策略组合运用可以在满足当前功能诉求的同时兼顾未来可能产生的变更请求,进而达到提高整体架构灵活性的目的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值