我们知道,Spring MVC
应用中,当通过如下方式定义一个控制器方法时,参数列表中的ServletRequest request
无论放到参数列表的哪个位置,在方法体内,它都正确地代表当前请求对象,你有没有想过,这背后的原理是什么呢 ?
@Controller
public class DemoController {
@RequestMapping(value = "/")
public String myControllerMethod(ServletRequest request) {
// 这里可以访问方法参数 request,
// 它也代表当前请求对象
// ...
}
}
通过分析Spring MVC
的源代码,概括地讲,在调用控制器方法之前,Spring MVC
先从请求上下文中通过配置的各种HandlerMethodArgumentResolver
解析得到了每个方法参数的值(这当然包含参数列表中的ServletRequest request
),然后才调用控制器方法,这样一来,方法体内自然能通过参数request
访问到正确的请求对象了。
关于Spring MVC
完整的调用一个控制器方法的过程分析,可以参考我的另外一个系列文章:Spring MVC : 控制器方法处理请求的过程分析 - 0. 概述,本文对此不在展开分析。本文中,我们主要关注Spring MVC
具体用来处理参数ServletRequest request
的那个HandlerMethodArgumentResolver
:ServletRequestMethodArgumentResolver
。
接下来,我们来看ServletRequestMethodArgumentResolver
的源代码。
首先,作为一个HandlerMethodArgumentResolver
,它声明支持哪些特征的参数的方法实现为 :
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) || // <=== 看这里,看这里,看这里
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
从此方法实现不难看出,如果一个参数的类型是ServletRequest
的可赋值类型,它的解析工作就会由ServletRequestMethodArgumentResolver
来负责。
当然,你可能也注意到了,
ServletRequestMethodArgumentResolver
在这里声明支持很多其他类型的参数,比如HttpSession
之类。是的,这正是为什么我们要以ServletRequest
作为例子来讲解的原因,因为通过这个例子,你可以举一反三地理解很多其他类型参数是如何被处理的。
我们继续来看ServletRequest
类型的参数被ServletRequestMethodArgumentResolver
解析的过程:
// ServletRequestMethodArgumentResolver 代码片段
// webRequest 是 Spring MVC 在处理一个请求过程中代表当前请求的对象,
// 在 Spring MVC 使用某个 HandlerMethodArgumentResolver 解析控制器方法的某个参数时,
// 总是会将 webRequest 传递给该 HandlerMethodArgumentResolver。
// mavContainer, webRequest , binderFactory 共同组成了解析指定参数 parameter
// 的一个上下文环境。
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// WebRequest / NativeWebRequest / ServletWebRequest
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
}
// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
// 如果参数的类型为 ServletRequest 的可赋值类型时,委托方法 resolveNativeRequest 做具体解析
if (ServletRequest.class.isAssignableFrom(paramType)
|| MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
// HttpServletRequest required for all further argument types
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
作为接口HandlerMethodArgumentResolver
定义的方法参数解析函数,resolveArgument
首先会被传递参数NativeWebRequest webRequest
,它代表了当前请求对象。从该方法的实现逻辑可见,如果控制器方法参数的类型为ServletRequest
的可赋值类型时,它会委托方法resolveNativeRequest
做具体的参数值解析。我们继续来看 :
// ServletRequestMethodArgumentResolver 代码片段
private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
// requiredType 就是开发人员在控制器方法中指定的request 的类型,在上面的例子中,它就是 ServletRequest,
// 实际上,它也可以继承自 ServletRequest 的某个子类
T nativeRequest = webRequest.getNativeRequest(requiredType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
}
return nativeRequest;
}
通过分析ServletRequestMethodArgumentResolver
以上几个方法的源代码,控制器方法中的参数ServletRequest
值从何而来,是不是就很明白了呢 ?