本系列文章的上一篇 : 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
会被执行。它的执行流程如下 :
- 执行方法参数验证逻辑
验证失败时抛出异常
ConstraintViolationException
携带相应错误信息。 - 调用目标方法
- 执行方法返回值验证逻辑
验证失败时抛出异常
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)
调用就会事实上退化成仅仅对目标控制器方法的调用。
本系列文章链接合集 :