1. 概述
本文接 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》 一文,我们来分享 HandlerMethodArgumentResolver ,HandlerMethod 的参数解析器接口。代码如下:
// HandlerMethodArgumentResolver.java |
- 两个方法,分别是是否支持解析该参数、以及解析该参数。
2. 类图
HandlerMethodArgumentResolver 的实现类非常多,如下图所示:HandlerAdapter 类图
因为实在太大,胖友可以点击 传送 查看。
下面,我要说什么化,想必熟悉我的胖友已经知道了,我们就分析几个 HandlerMethodArgumentResolver 实现类。哈哈哈哈
3. HandlerMethodArgumentResolverComposite
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
,实现 HandlerMethodArgumentResolver 接口,复合的 HandlerMethodArgumentResolver 实现类。
3.1 构造方法
// HandlerMethodArgumentResolverComposite.java |
argumentResolvers
属性,HandlerMethodArgumentResolver 数组。这就是 Composite 复合~argumentResolverCache
属性,MethodParameter 与 HandlerMethodArgumentResolver 的映射,作为缓存。因为,MethodParameter 是需要从argumentResolvers
遍历到适合其的解析器,通过缓存后,无需再次重复遍历。
另外,在 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(一)之 HandlerAdapter》 的 「7.2.2 getDefaultArgumentResolvers」 中,我们已经看到了,HandlerMethodArgumentResolverComposite 默认复合的所有 HandlerMethodArgumentResolver 对象。😈 忘记的胖友,可以点下 传送门 再瞅瞅。
3.2 getArgumentResolver
#getArgumentResolver(MethodParameter parameter)
方法,获得方法参数对应的 HandlerMethodArgumentResolver 对象。代码如下:
// HandlerMethodArgumentResolverComposite.java |
3.3 supportsParameter
实现 #supportsParameter(MethodParameter parameter)
方法,如果能获得到对应的 HandlerMethodArgumentResolver 处理器,则说明支持。代码如下:
// HandlerMethodArgumentResolverComposite.java |
3.4 resolveArgument
#resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
方法,解析指定参数的值。代码如下:
// HandlerMethodArgumentResolverComposite.java |
4. AbstractNamedValueMethodArgumentResolver
org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
,实现 ValueMethodArgumentResolver 接口,基于名字获取值的HandlerMethodArgumentResolver 抽象基类。例如说,@RequestParam(value = "username")
注解的参数,就是从请求中获得 username
对应的参数值。😈 明白了么?
AbstractNamedValueMethodArgumentResolver 的子类不多,如下图所示:AbstractNamedValueMethodArgumentResolver 类图
😝 虽然不多,但是我们仅仅分析常用的,分别是:
- RequestParamMethodArgumentResolver ,基于
@RequestParam
注解( 也可不加该注解的请求参数 )的方法参数,在 「5. RequestParamMethodArgumentResolver」 中,详细解析。 - PathVariableMethodArgumentResolver ,基于
@PathVariable
注解的方法参数,在 「6. PathVariableMethodArgumentResolver」 中,详细解析。
4.1 构造方法
// AbstractNamedValueMethodArgumentResolver.java |
namedValueInfoCache
属性,MethodParameter 和 NamedValueInfo 的映射,作为缓存。
4.2 NamedValueInfo
NamedValueInfo ,是 AbstractNamedValueMethodArgumentResolver 的静态类,代码如下:
// AbstractNamedValueMethodArgumentResolver.java |
4.3 getNamedValueInfo
#getNamedValueInfo(MethodParameter parameter)
方法,获得方法参数对应的 NamedValueInfo 对象。代码如下:
// AbstractNamedValueMethodArgumentResolver.java |
<1>
处,从namedValueInfoCache
缓存中,获得 NamedValueInfo 对象。<2>
处,获得不到,则调用#createNamedValueInfo(MethodParameter parameter)
方法,创建 namedValueInfo 对象。这是一个抽象方法,子类来实现。代码如下:// AbstractNamedValueMethodArgumentResolver.java
/**
* Create the {@link NamedValueInfo} object for the given method parameter. Implementations typically
* retrieve the method annotation by means of {@link MethodParameter#getParameterAnnotation(Class)}.
* @param parameter the method parameter
* @return the named value information
*/
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
<3>
处,调用#updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info)
方法,更新namedValueInfo
对象。代码如下:// AbstractNamedValueMethodArgumentResolver.java
/**
* Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
*/
private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
// 如果名字为空,则抛出 IllegalArgumentException 异常
if (info.name.isEmpty()) {
name = parameter.getParameterName(); // 【注意!!!】如果 name 为空,则使用参数名
if (name == null) {
throw new IllegalArgumentException(
"Name for argument type [" + parameter.getNestedParameterType().getName() +
"] not available, and parameter name information not found in class file either.");
}
}
// 获得默认值
String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
// 创建 NamedValueInfo 对象
return new NamedValueInfo(name, info.required, defaultValue);
}- 最终,会创建一个新的 NamedValueInfo 对象。
<4>
处,添加到namedValueInfoCache
缓存中。
4.4 resolveArgument
#resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
方法,解析指定参数的值。代码如下:
// AbstractNamedValueMethodArgumentResolver.java |
<1>
处,调用#getNamedValueInfo(MethodParameter parameter)
方法,获得方法参数对应的 NamedValueInfo 对象。也就是说,在 「4.3 getNamedValueInfo」 中。<2>
处,如果parameter
是内嵌类型的,则获取内嵌的参数。否则,还是使用parameter
自身。一般情况下,parameter
参数,我们不太会使用 Optional 类型。所以,胖友也可以不理解。<3>
处,调用#resolveStringValue(String value)
方法,如果name
是占位符,则进行解析成对应的值。代码如下:// AbstractNamedValueMethodArgumentResolver.java
private Object resolveStringValue(String value) {
// 如果 configurableBeanFactory 为空,则不进行解析
if (this.configurableBeanFactory == null) {
return value;
}
// 如果 exprResolver 或 expressionContext 为空,则不进行解析
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null || this.expressionContext == null) {
return value;
}
// 获得占位符对应的值
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
// 计算表达式
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}这种用法非常小众,艿艿重来没用过。示例如下:
// Controller.java
"/hello3") (
public String hello3(@RequestParam(value = "${server.port}") String name) {
return "666";
}
// application.properties
server.port=8012- 此时,就可以
GET /hello3?8012=xxx
。
- 此时,就可以
<4>
处,调用#resolveName(String name, MethodParameter parameter, NativeWebRequest request)
抽象方法,解析name
对应的值。代码如下:// AbstractNamedValueMethodArgumentResolver.java
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception;
<5>
处,如果arg
为null
,则使用默认值。<5.1>
处,如果默认值非空,则调用#resolveStringValue(defaultValue)
方法,解析默认值。<5.2>
处,如果是必填,则调用#handleMissingValue(handleMissingValue)
方法,处理参数缺失的情况调用。代码如下:// AbstractNamedValueMethodArgumentResolver.java
protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception {
handleMissingValue(name, parameter);
}
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
throw new ServletRequestBindingException("Missing argument '" + name +
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
}- 抛出 ServletRequestBindingException 异常。
- 另外,子类 RequestParamMethodArgumentResolver 会重写该方法。
<5.3>
处,调用#handleNullValue(String name, Object value, Class<?> paramType)
方法,处理null
值的情况。代码如下:// AbstractNamedValueMethodArgumentResolver.java
/**
* A {@code null} results in a {@code false} value for {@code boolean}s or an exception for other primitives.
*/
private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
if (value == null) {
// 如果是 Boolean 类型,则返回 FALSE
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
// 如果基本类型,因为 null 无法转化,则抛出 IllegalStateException 异常
} else if (paramType.isPrimitive()) {
throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
"' is present but cannot be translated into a null value due to being declared as a " +
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
}
}
// 返回默认值
return value;
}- 牛逼,各种细节的处理啊。
<6>
处,逻辑同<5.1>
处。<7>
处,执行值的类型转换。TODO 1016<8>
处,调用#handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
方法,解析值后的后置处理。代码如下:// AbstractNamedValueMethodArgumentResolver.java
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
}- 默认为空方法。
- 另外,子类 PathVariableMethodArgumentResolver 会重写该方法。
艿艿:代码有丢丢长,实际逻辑是不复杂的。胖友在回看下。
5. RequestParamMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
,实现 UriComponentsContributor 接口,继承 AbstractNamedValueMethodArgumentResolver 抽象类,请求参数的 HandlerMethodArgumentResolver 实现类,处理普通的请求参数。
5.1 构造方法
// RequestParamMethodArgumentResolver.java |
5.2 supportsParameter
实现 #supportsParameter(MethodParameter parameter)
方法,代码如下:
// RequestParamMethodArgumentResolver.java |
- 情况有点多,胖友耐心看下注释。我们只讲里面的几处调用。
<1>
处,调用MultipartResolutionDelegate#isMultipartArgument(parameter)
方法,如果 Multipart 参数。则返回true
,表示支持。代码如下:// MultipartResolutionDelegate.java
public static boolean isMultipartArgument(MethodParameter parameter) {
Class<?> paramType = parameter.getNestedParameterType();
// MultipartFile 的多种情况
return (MultipartFile.class == paramType || isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
// Part 的多种情况
(Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
}- 上传文件相关的类型。
<2>
处,如果开启useDefaultResolution
功能,则调用BeanUtils#isSimpleProperty(Class<?> clazz)
方法,判断是否为普通类型。代码如下:// BeanUtils.java
public static boolean isSimpleProperty(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
Enum.class.isAssignableFrom(clazz) ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}
继续补充
<2>
处,那么useDefaultResolution
到底是怎么样的赋值呢?回到 RequestMappingHandlerAdapter 的#getDefaultArgumentResolvers()
的方法,精简代码如下:// RequestMappingHandlerAdapter.java
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
// .. 省略一大片
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
// .. 省略几个
return resolvers;
}- 我们可以看到有两个 RequestParamMethodArgumentResolver 对象,前者
useDefaultResolution
为false
,后者为useDefaultResolution
为true
。什么意思呢?优先将待有@RequestParam
注解的请求参数给第一个 RequestParamMethodArgumentResolver 对象;其次,给中间省略的一大片参数解析器试试能不能解析;最后,使用第二个 RequestParamMethodArgumentResolver 兜底,处理剩余的情况。
- 我们可以看到有两个 RequestParamMethodArgumentResolver 对象,前者
<3>
处,如果是 Map 类型,则 @RequestParam 注解必须要有name
属性。是不是感觉有几分灵异?答案在 「6. RequestParamMapMethodArgumentResolver」 一起讲。
5.3 createNamedValueInfo
#createNamedValueInfo(MethodParameter parameter)
方法,创建 NamedValueInfo 对象。代码如下:
// RequestParamMethodArgumentResolver.java |
- 虽然,在无
@RequestMapping
时,返回的 RequestParamNamedValueInfo 对象的name
属性为""
,但是胖友在回过头看 「4.3 getNamedValueInfo」 ,有一个“【注意】!!!”的地方。😈
5.4 resolveName
实现 #resolveName(String name, MethodParameter parameter, NativeWebRequest request)
方法,获得参数的值。代码如下:
// RequestParamMethodArgumentResolver.java |
- 情况一、二,是处理参数类型为文件
org.springframework.web.multipart.MultipartFile
和javax.servlet.http.Part
的参数的获取。 - 情况三,是处理普通参数的获取。就是我们常见的 String、Integer 之类的请求参数。
5.5 handleMissingValue
重写 #handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
方法,代码如下:
// RequestParamMethodArgumentResolver.java |
- 根据参数的类型,做更详细的异常抛出。
6. RequestParamMapMethodArgumentResolver
org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver
,实现 HandlerMethodArgumentResolver 接口,处理带有 @RequestParam
注解,但是注解上无 name
属性的 Map 类型的参数的 RequestParamMethodArgumentResolver 实现类。代码如下:
// RequestParamMapMethodArgumentResolver.java |
- 具体的代码实现,胖友自己瞅瞅。当然,良心如艿艿,还是会提供示例。
① 对于 RequestParamMapMethodArgumentResolver 类,它的效果是,将所有参数添加到 Map 集合中。示例如下:
// Controller.java |
GET /hello4?name=yyy&age=20
的name
和age
参数,就会都添加到map
中。
② 对于 RequestParamMethodArgumentResolver 类,它的效果是,将指定名字的参数添加到 Map 集合中。示例如下:
// Controller.java |
GET /hello4?map={"name": "yyyy", age: 20}
的map
参数,就会都添加到map
中。当然,要注意下,实际请求要 UrlEncode 编码下参数,所以实际请求是GET /hello4?map=%7b%22name%22%3a+%22yyyy%22%2c+age%3a+20%7d
。
7. PathVariableMethodArgumentResolver
org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
,实现 UriComponentsContributor 接口,继承 AbstractNamedValueMethodArgumentResolver 抽象类,处理路径参数。
7.1 supportsParameter
实现 #supportsParameter(MethodParameter parameter)
方法,代码如下:
// PathVariableMethodArgumentResolver.java |
- 大体逻辑,胖友自己看代码。
<x>
处,又出了上面刚刚说过的 Map 的情况,胖友自己去对比org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver
类。
7.2 createNamedValueInfo
实现 #createNamedValueInfo(MethodParameter parameter)
方法,代码如下:
// PathVariableMethodArgumentResolver.java |
7.3 resolveName
实现 #resolveName(String name, MethodParameter parameter, NativeWebRequest request)
方法,代码如下:
// PathVariableMethodArgumentResolver.java |
7.4 handleMissingValue
重写 #handleMissingValue()
方法,抛出 MissingPathVariableException 异常。代码如下:
// PathVariableMethodArgumentResolver.java |
7.5 handleResolvedValue
重写 #handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request)
方法,添加获得的属性值到请求的 View.PATH_VARIABLES
属性种。代码如下:
// PathVariableMethodArgumentResolver.java |
666. 彩蛋
感觉,还有一些需要写的 HandlerMethodArgumentResolver 实现类,暂时还没想好。
如果胖友有什么 HandlerMethodArgumentResolver 实现类,希望艿艿来写,请在星球给我留言。TODO 9999 HandlerMethodArgumentResolver
参考和推荐如下文章: