【Spring MVC】MVC请求的处理过程一览-HandlerMethod
1 前言
我们整体看了下 MVC 的请求过程以及上节看了下执行器的 RequestMappingHandlerAdapter 的处理过程,我们看到了 ServletInvocableHandlerMethod 这个类,我们本节整体看一下 HandlerMethod 体系,核心属性以及执行过程。
2 HandlerMethod一览
ServletInvocableHandlerMethod 的类关系比较简单,我们看下:
ServletInvocableHandlerMethod 其实也是一种HandlerMethod,只是增加了方法执行的功能。当然相应地也增加了参数解析、返回值处理等相关功能。本节就先从HandlerMethod 开始依次对这三个组件进行分析。
2.1 HandlerMethod
HandlerMethod 是用于封装 Handler 和其中具体处理请求的Method,比如我们 Controller 里的每个带 @RequestMapping 的方法都会封装成这样的对象,他内部除了持有对应的bean 和 method 属性,还有三个属性:beanFactory、bridgedMethod 和 parameters。beanFactory 主要用于新建 HandlerMethod 时传入的Handler(也就是bean 属性)是 String 的情况,这时需要使用 beanFactory根据传人的String作为 beanName 获取到对应的bean,并设置为 Handler;bridgedMethod 指如果 method 是bridge method 则设置为其所对应的原有方法,否则直接设置力 method(这点你可以理解为就是你 Controller 里的某个方法,至于为什么这么叫,我们下去再研究); parameters 代表处理请求的方法的参数。定义如下:
// HandlerMethod 核心属性 // 就是你的 Controller Bean private final Object bean; // bean 工厂主要是起个转换以及获取某些东西 private final BeanFactory beanFactory; // 你的方法 private final Method method; private final Method bridgedMethod; // 方法的参数 这里是用 MethodParameter 这个对象包装每个参数的 // 有几个参数 数组就有几个这样的对象 private final MethodParameter[] parameters;
都是核心属性哈,除了 MethodParameter,其他几个基本都能知道,我们再多看看 MethodParameter。
2.1.1 MethodParameter 概述
我们的 HandlerMethod 的 MethodParameter,是他的子类 HandlerMethodParameter 位于 HandlerMethod 的内部类,他的类图如下:
可以看到相对于父类多扩展了一个注解数组,我们看下他内部的核心属性:
// MethodParameter 核心属性 // 这个其实就是你的 Method 表示是哪个方法的参数 private final Executable executable; // 参数位置 也就是第几个参数 从0开始 private final int parameterIndex; // 参数所在类 也就是方法所在的类 private volatile Class<?> containingClass; // 参数类型 private volatile Class<?> parameterType; // 参数的注解 private volatile Annotation[] parameterAnnotations; // 参数名字 private volatile String parameterName; // 方法的参数 反射的类 private volatile Parameter parameter;
2.1.2 MethodParameter 初始化时机
我们顺便再回顾下他的初始化时机,我们在看 【Spring MVC】MVC请求的处理过程一览 其实有看到过,我们回顾下:
在 HandlerMethod 的构造方法中,会进行参数的初始化,如下:
// HandlerMethod 构造方法 public HandlerMethod(String beanName, BeanFactory beanFactory, @Nullable MessageSource messageSource, Method method) { Assert.hasText(beanName, "Bean name is required"); Assert.notNull(beanFactory, "BeanFactory is required"); Assert.notNull(method, "Method is required"); this.bean = beanName; this.beanFactory = beanFactory; this.messageSource = messageSource; Class<?> beanType = beanFactory.getType(beanName); if (beanType == null) { throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'"); } this.beanType = ClassUtils.getUserClass(beanType); this.method = method; this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); ReflectionUtils.makeAccessible(this.bridgedMethod); // 初始化方法参数 this.parameters = initMethodParameters(); evaluateResponseStatus(); this.description = initDescription(this.beanType, this.method); }
我们继续看完:
// HandlerMethod#initMethodParameters private MethodParameter[] initMethodParameters() { // 获取方法参数的个数、创建数组 int count = this.bridgedMethod.getParameterCount(); MethodParameter[] result = new MethodParameter[count]; // 给每个参数创建一个 HandlerMethodParameter for (int i = 0; i < count; i++) { result[i] = new HandlerMethodParameter(i); } return result; } // HandlerMethodParameter 构造方法 public HandlerMethodParameter(int index) { // 直接调用父类 super(HandlerMethod.this.bridgedMethod, index); } // SynthesizingMethodParameter 构造方法 public SynthesizingMethodParameter(Method method, int parameterIndex) { super(method, parameterIndex); } // MethodParameter 构造方法 public MethodParameter(Method method, int parameterIndex) { this(method, parameterIndex, 1); } // MethodParameter 重载的构造方法 public MethodParameter(Method method, int parameterIndex, int nestingLevel) { Assert.notNull(method, "Method must not be null"); // 你的方法 this.executable = method; // 参数位置 默认从0开始 this.parameterIndex = validateIndex(method, parameterIndex); // 嵌套级别 默认从1开始 如果是复合参数会用到,比如,有一个 List<String>params 参数,则 params 的嵌套级别为 1,List 内部的 String 的嵌套级别为2 this.nestingLevel = nestingLevel; }
方法参数就暂时了解到这里,看了 HandlerMethod 我们继续看他的子类 InvocableHandlerMethod。
2.2 InvocableHandlerMethod
InvocableHandlerMethod 继承自 HandlerMethod,在父类基础上添加了调用的功能,也就是说 InvocableHandlerMethod 可以直接调用内部属性 method 对应的方法(严格来说应该是bridgedMethod)。
InvocableHandlerMethod 里增加了三个属性:
dataBinderFactory : WebDataBinderFactory 类型,可以创建 WebDataBinder,用于参数解析器 ArgumentResolver中。
argumentResolvers: HandlerMethodArgumentResolverComposite 类型,用于解析参数。
parameterNameDiscoverer: ParameterNameDiscoverer 类型,用来获取参数名,用于MethodParameter中。
InvocableHandlerMethod 中 Method 调用的方法是 invokeForRequest,代码如下:
// InvocableHandlerMethod#invokeForRequest() public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 解析方法的参数 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } // 执行你的 controller 的内容 return doInvoke(args); }
这个方法非常简单,一条是准备方法所需要的参数,使用的是 getMethodArgumentValues 方法,另一条用于具体调用 Method,具体使用的方法是dolnvoke 方法。dolnvoke 方法是实际执行请求处理的方法,我们写的代码都是通过它来执行的,所以这个方法是整个 HandlerMethod 系列的处理器中最核心的方法,不过它的代码非常简单,如下所示:
// InvocableHandlerMethod#doInvoke() protected Object doInvoke(Object... args) throws Exception { // 我们 controller 里的方法 Method method = getBridgedMethod(); try { if (KotlinDetector.isSuspendingFunction(method)) { return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); } // 就是反射执行我们的方法 return method.invoke(getBean(), args); } catch (IllegalArgumentException ex) { assertTargetBean(method, getBean(), args); String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); throw new IllegalStateException(formatInvokeError(text, args), ex); } catch (InvocationTargetException ex) { // Unwrap for HandlerExceptionResolvers ... 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); } } }
除了异常处理外,真正执行的方法就是直接调用 bridgedMethod 的 invoke 方法,反射执行。再返回来看一下参数绑定的 getMethodArgumentValues 方法,代码如下:
// InvocableHandlerMethod#getMethodArgumentValues() protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 获取到方法的参数 就是我们上边看到的 HandlerMethod 的 parameters 属性 // private final MethodParameter[] parameters; MethodParameter[] parameters = getMethodParameters(); // 空的话直接返回 if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } // 存放最后解析出来的参数 Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { // 逐个取出进行解析 MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // providedArgs没看出来什么意思 这玩意默认是空的 args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 看看是否有这种参数的解析器没有的话直接报错 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { // 解析参数 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
通过注释大家就容易理解了,这里首先调用父类的getMethodParameters 方法获取到Method 的所有参数,然后定义了 Object 数组变量 args 用于保存解析出的参数值。接下来遍历每个参数进行解析,解析的方法有两种,第一种是在providedArgs 里面找,第二种是使用 argumentResolvers 解析,在 RequestMappingHandlerAdapter 中的调用并没有提供providedArgs,所以只有使用 argumentResolvers 解析。每个参数在解析前都初始化了三个属性:parameterNameDiscoverer、containingClass 和 parameterType, parameterNameDiscoverer 用于获取参数名,可以在 RequestMappingHandlerAdapter 定义时配置,默认使用 DefaultParameterNameDiscoverer。 containingClass 和 parameterType 在前面已经介绍过了,分别表示容器类型(也就是所属的类)和参数类型。如果没有解析出参数值最后会抛出 IIlegalStateException 异常。
InvocableHandlerMethod 就分析完了,它就是在 HandlerMethod 的基础上添加的方法调用的功能,而方法调用又需要解析参数,所以又提供了解析参数的功能。
2.3 ServletInvocableHandlerMethod
ServletInvocableHandlerMethod 继承自 InvocableHandlerMethod,在父类基础上增加了三个功能:①对 @ResponseStatus 注释的支持;②对返回值的处理;③对异步处理结果的处理。
@ResponseStatus 注释用于处理器方法或者返回值上,作用是对返回 Response 的 Status 进行设置,它有两个参数:value 和 reason,value 是 HttpStatus 类型,不能为空,reason 是 String类型,表示错误的原因,默认空字符串(不是 null)。当一个方法注释了 @ResponseStatus后,返回的 response 会使用注释中的 Status,如果处理器返回值空或者 reason 不空,则将中断处理直接返回(不再渲染页面)。实际环境中用的并不是很多。
对返回值的处理是使用 returnValueHandlers 属性完成的,它是 HandlerMethodReturnValueHandler类型的属性。
ServletinvocableHandlerMethod 处理请求使用的是 invokeAndHandle 方法,代码如下:
// ServletinvocableHandlerMethod#invokeAndHandle() public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 执行 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
处理返回值的逻辑是先判断返回值是不是 null,如果是 null,只要 request 的 notModified为真、注释了 @ResponseStatus 和 mavContainer 的 requestHandled 为 true 这三项中有一项成立则设置请求已经处理并返回,如果返回值不为 null,而 @ResponseStatus 注释里存在reason,也会将请求设置为已处理并返回。设置已处理的方法前面已经讲过,就是设置mavContainer 的 requestHandled 属性力 true。如果上面那些条件都不成立则将 mavContainer 的requestHlandled 设置力 false,并使用 returnValueHlandlers 处理返回值。
到这里调用处理器处理请求的过程就讲完了。下节分析解析参数的 HandlerMethodArgumentResolver 和处理返回值的 HandlerMethodReturValueHandler。
3 小结
本节我们主要看了下 HandlerMethod 从上到下的执行,我们这里再画个图捋一下:
有理解不对的地方欢迎指正哈。