参数解析原理
一、是什么 ------ 参数解析到底在做什么?
1. 一句话定义
参数解析就是 Spring MVC 在调用 Controller 方法之前,根据方法参数的类型、注解等信息,从 HTTP 请求中提取出对应的值,然后通过反射传给方法的过程。
2. 用大白话理解
你的 Controller 方法可以声明五花八门的参数:
java
// 源码位置:springboot2-master/boot-05-web-01/.../controller/ParameterTestController.java
@GetMapping("/car/{id}/owner/{username}")
public Map<String,Object> getCar(@PathVariable("id") Integer id, // 来自路径
@PathVariable("username") String name, // 来自路径
@RequestParam("age") Integer age, // 来自查询参数
@RequestHeader("User-Agent") String ua, // 来自请求头
@CookieValue("_ga") Cookie cookie) { // 来自 Cookie
// ...
}
Spring 是怎么知道:
id要从 URL 路径/car/2/owner/zhangsan中提取2age要从?age=18中提取18ua要从请求头User-Agent中提取
答案就是:参数解析器(HandlerMethodArgumentResolver)体系。
3. 核心接口
java
public interface HandlerMethodArgumentResolver {
// 判断:这个解析器能处理这个参数吗?
boolean supportsParameter(MethodParameter parameter);
// 解析:如果能处理,就解析出参数的值
Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception;
}
二、为什么 ------ 为什么要用这套机制?
1. 不这么做会怎样?
如果没有参数解析器,你需要自己在每个 Controller 方法开头写:
java
// 手动解析,噩梦!
String idStr = request.getParameter("id");
Integer id = Integer.parseInt(idStr);
String name = request.getParameter("name");
// ... 每个参数都要写,还要处理 null、类型转换、默认值
几百个 Controller 方法,几千行重复代码。
2. 设计哲学:策略模式 + 组合模式
Spring 把"解析参数"这个行为抽象成策略接口(HandlerMethodArgumentResolver),然后注册了几十个具体策略(@RequestParam的、@PathVariable的、@RequestBody的......)。
需要解析时,遍历所有策略,找到第一个支持的,就交给它执行。
三、怎么做 ------ 参数解析的完整源码流程
1. HandlerAdapter 的选取(getHandlerAdapter)
在 doDispatch() 中,拿到 Handler 之后,需要找到能执行它的适配器:
java
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
这也是遍历匹配的:
java
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) { // 询问每个适配器
return adapter; // 找到就返回
}
}
}
throw new ServletException("No adapter for handler...");
}
对于 @RequestMapping 标注的方法,handler 是 HandlerMethod 类型,命中的是 RequestMappingHandlerAdapter:
java
// RequestMappingHandlerAdapter 的 supports 判断
public final boolean supports(Object handler) {
return handler instanceof HandlerMethod
&& this.supportsInternal((HandlerMethod) handler);
}
2. RequestMappingHandlerAdapter 的准备工作
适配器内部已经准备好了两套"兵工厂":
java
// RequestMappingHandlerAdapter.invokeHandlerMethod() 中
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// ★ 关键:注入参数解析器(27个)
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// ★ 关键:注入返回值处理器(15个)
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
默认的 argumentResolvers 有 27 个,包含:
| 解析器 | 处理什么 |
|---|---|
RequestParamMethodArgumentResolver |
@RequestParam 注解 |
PathVariableMethodArgumentResolver |
@PathVariable 注解 |
RequestResponseBodyMethodProcessor |
@RequestBody 注解 |
ServletRequestMethodArgumentResolver |
HttpServletRequest 类型 |
ServletModelAttributeMethodProcessor |
自定义 POJO 对象 |
MapMethodProcessor |
Map 类型参数 |
ModelMethodProcessor |
Model 类型参数 |
| ...... | ...... |
3. 执行入口:invokeAndHandle()
java
// ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// ★ 第一步:解析参数 + 反射调用 Controller 方法
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
// 第二步:处理返回值
this.returnValueHandlers.handleReturnValue(...);
}
4. 核心!getMethodArgumentValues() ------ 逐行拆解
java
protected Object[] getMethodArgumentValues(NativeWebRequest request,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 步骤 1:获取方法的所有参数元数据
MethodParameter[] parameters = this.getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS; // 步骤 2:无参方法直接返回空数组
}
// 步骤 3:初始化参数值数组
Object[] args = new Object[parameters.length];
// 步骤 4:逐个遍历参数
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 步骤 5:确保参数名可用(通过 ASM 或 JDK8+ -parameters 编译标志)
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 步骤 6:检查外部预提供的参数(如测试环境预置的 Model)
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] == null) {
// 步骤 7:判断是否有解析器支持当前参数
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException("No suitable resolver for: " + parameter);
}
// 步骤 8:调用解析器解析参数值
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer,
request, this.dataBinderFactory);
} catch (Exception ex) {
throw ex;
}
}
}
// 步骤 9:返回参数数组 → 用于反射调用
return args;
}
5. 解析器匹配的内部机制 ------ supportsParameter()
this.resolvers 实际上是 HandlerMethodArgumentResolverComposite(组合模式),它在内部遍历所有解析器:
java
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
// ★ 缓存优先:同一个方法的同一个参数,不需要每次遍历
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
// ★ 找到后放入缓存,下次 O(1) 直接命中
this.argumentResolverCache.put(parameter, resolver);
break;
}
}
}
return result;
}
性能优化的点睛之笔 :argumentResolverCache 是一个 ConcurrentHashMap。第一次请求时遍历 N 个解析器找到匹配的,后续相同方法的相同参数直接从缓存取,时间复杂度 O(1)。
6. 解析参数的通用模板 ------ resolveArgument()
对于 @RequestParam、@PathVariable、@RequestHeader 等命名参数 ,都继承自 AbstractNamedValueMethodArgumentResolver,使用了模板方法模式:
java
public final Object resolveArgument(MethodParameter parameter, ...) throws Exception {
// ① 获取命名值元数据(注解中的 name、required、defaultValue 等)
NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
// ② 解析名称中的占位符/SpEL 表达式
Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
// ③ ★ 核心取值(抽象方法,子类实现)
// @RequestParam → request.getParameter()
// @PathVariable → 从 URI 模板变量中取
// @RequestHeader → request.getHeader()
Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
// ④ 处理缺失值和默认值
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
// ⑤ ★ 类型转换(使用 WebDataBinder)
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
return arg;
}
7. 案例分析:HttpServletRequest 参数如何解析
java
// 源码位置:springboot2-master/boot-05-web-01/.../controller/RequestController.java
@GetMapping("/goto")
public String goToPage(HttpServletRequest request) {
request.setAttribute("msg", "成功了...");
return "forward:/success";
}
匹配的解析器 :ServletRequestMethodArgumentResolver
java
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return WebRequest.class.isAssignableFrom(paramType)
|| ServletRequest.class.isAssignableFrom(paramType) // ← HttpServletRequest
|| HttpSession.class.isAssignableFrom(paramType)
|| InputStream.class.isAssignableFrom(paramType)
|| Reader.class.isAssignableFrom(paramType)
// ... 其他 Servlet API 类型
;
}
解析逻辑:直接从 WebRequest 中取出原生 Request 返回:
java
private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
T nativeRequest = webRequest.getNativeRequest(requiredType);
if (nativeRequest == null) {
throw new IllegalStateException("Current request is not of type...");
}
return nativeRequest;
}
非常简单直接------HttpServletRequest 本身就在请求上下文中,不需要从参数中提取,直接返回引用即可。
四、总结 ------ 参数解析全景图
Controller 方法有参数需要解析
↓
getMethodArgumentValues() ------ 获取方法参数元数据数组
↓
for 循环遍历每个参数
↓
this.resolvers.supportsParameter(parameter)
├── 先查缓存(argumentResolverCache)
└── 缓存未命中 → 遍历 27 个解析器
├── supportsParameter() 询问每个解析器
└── 找到第一个支持的 → 放入缓存 + 执行 resolveArgument()
├── 从 Request 中提取原始值(String)
├── 处理默认值 / 缺失值
├── WebDataBinder.convertIfNecessary() 类型转换
└── 返回转换后的值
↓
args[] 数组组装完成
↓
doInvoke(args) → 反射调用 Controller 方法
一句话总结:
参数解析的本质就是策略模式 + 模板方法模式 + 缓存优化的三合一:遍历解析器列表找合适的策略,用模板方法统一处理"取值→默认值→类型转换"流程,用缓存保证 O(1) 的二次命中。