Spring MVC : 控制器方法处理请求的过程分析 - 4. 控制器方法参数值的验证 MethodValidationInterceptor

本文深入探讨SpringMVC框架中控制器方法参数值验证机制,解析如何利用Spring注解@Validated结合JSR-303注解进行参数有效性检查,及MethodValidationInterceptor在其中的作用。

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

本系列文章的上一篇 : Spring MVC : 控制器方法处理请求的过程分析 - 3. 控制器方法参数值绑定 HandlerMethodArgumentResolver

当从请求上下文中获取到目标控制器方法参数值列表之后,顺理成章地,下一步就是要调用目标控制器方法了,这一点可以从如下代码逻辑观察到:

    // InvocableHandlerMethod 代码片段
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

       // 此处逻辑是本系列以上几篇文章所介绍的请求参数值解析和绑定的逻辑,
       // 当此方法返回时,args 中的对象已经按照目标方法参数列表的结构(顺序,类型)
       // 组织好可以直接应用了
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
        
       // 现在使用 args 继续发起对目标控制器方法的调用,并返回其返回值 
		return doInvoke(args);
	}

此话不假,InvocableHandlerMethod#doInvoke实现如下 :

	@Nullable
	protected Object doInvoke(Object... args) throws Exception {
       // 确保目标控制器方法是可以访问的 
		ReflectionUtils.makeAccessible(getBridgedMethod());
		try {
           // 调用目标控制器方法 
			return getBridgedMethod().invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
           // 抛出参数格式错误导致的异常 
			assertTargetBean(getBridgedMethod(), getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
          // 对 InvocationTargetException 异常的处理
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

从这里不难看出,#doInvoke方法确实是对目标控制器方法发起了调用。那么,本文要分析的控制器方法参数值验证的逻辑又发生在哪里呢?这里有一点这里需要指出,那就是Spring代理机制在这里会起作用,具体来讲,会导致上面方法中#getBean方法返回的未必是目标控制器bean自身,而有可能是目标控制器bean对象的代理对象。对控制器方法参数值进行验证的机制,正是利用这一点,在控制器bean外包裹了一个MethodValidationInterceptor方法调用拦截器,该拦截器会拦截目标方法的调用,从而执行相应的JSR-303验证逻辑。

以一个Spring Boot + Spring MVC的应用为例,当一个控制器类上使用了Spring注解@Validated,该控制器组件bean就会被包裹一个MethodValidationInterceptor用以对控制器方法参数和返回值进行指定的验证逻辑:

/**
 * 注意:如果要在验证控制器方法上参数,必须要在类级别使用注解  @Validated
 */
@Validated
@RestController
public class TestController {
   
    /**      
     * 一个用于演示的控制器方法
     */
    @RequestMapping(value = "/new-comments")
    public Object newComments(           
            // 这里可以是一个JSR-303 验证注解,或者某个 Validation Provider 自己提供的注解
            // 该参数需要类级别有注解 @Validated
            @Size(min = 4, max = 255)
            @RequestParam(value = "comments") String comments) {
		// ...
    }

}

对于该例子,容器中会存在一个控制器组件bean testController,同时也会存在一个它的代理对象,该代理对象可以想象成一个MethodValidationInterceptor包裹在bean testController的外面。当用户请求/new-comments时,MethodValidationInterceptor#invoke会被执行。它的执行流程如下 :

  1. 执行方法参数验证逻辑

    验证失败时抛出异常ConstraintViolationException 携带相应错误信息。

  2. 调用目标方法
  3. 执行方法返回值验证逻辑

    验证失败时抛出异常ConstraintViolationException 携带相应错误信息。

关于MethodValidationInterceptor更全面的分析介绍可以参考 : Spring Context : MethodValidationInterceptor,这里就不再详细展开。而关于MethodValidationInterceptor何时被引入,又何时被包裹到目标组件bean上,所使用的验证器是什么,可以参考:

注意 : MethodValidationInterceptor不是一个Spring MVC层面的概念,不要跟Spring MVC HandlerInterceptor混淆。MethodValidationInterceptor是一个AOP概念,它既可以应用到一个Spring MVC控制器bean组件,也可以应用到某个一般的使用了@Component定义的bean组件。

当然,控制器方法层面也有可能不对参数进行验证,从实现上来讲,也就是去掉控制器类上的注解@Validated,这样一来MethodValidationInterceptor就不会被包裹在目标控制器bean组件外面了,相应地,如果目标控制器bean组件外面未包裹任何拦截器,上面的
getBridgedMethod().invoke(getBean(), args)调用就会事实上退化成仅仅对目标控制器方法的调用。

本系列文章链接合集 :

  1. 概述
  2. 执行信息记录对象ModelAndViewContainer的准备
  3. 请求参数的获取
  4. 控制器方法参数值绑定 HandlerMethodArgumentResolver
  5. 控制器方法参数值的验证 MethodValidationInterceptor
  6. 调用控制器方法本身
  7. 控制器方法返回值处理
  8. 包装返回结果 : 从ModelAndViewContainer对象构造ModelAndView对象

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值