抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

SpringMVC-参数解析器

前言

​ 在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Controller的handler方法参数能够自动完成参数封装(有时即使没有@PathVariable、@RequestParam、@RequestBody等注解都可),甚至在方法参数任意位置写HttpServletRequest、HttpSession、Writer…等类型的参数,它自动就有值了便可直接使用。
对此你是否想问一句:Spring MVC它是怎么办到的?那么本文就揭开它的神秘面纱,还你一片”清白”。

​ Spring MVC作为一个最为流行的web框架,早早已经成为了实际意义上的标准化(框架),特别是随着Struts2的突然崩塌,Spring MVC几乎一骑绝尘,因此深入了解它有着深远的意义

​ Spring MVC它只需要区区几个注解就能够让一个普通的java方法成为一个Handler处理器,并且还能有自动参数封装、返回值视图处理/渲染等一系列强大功能,让coder的精力更加的聚焦在自己的业务。

HandlerMethodArgumentResolver

​ 策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验等等。有了它才能会让Spring MVC处理入参显得那么高级、那么自动化。
​ Spring MVC内置了非常非常多的实现,当然若还不能满足你的需求,你依旧可以自定义和自己注册,后面我会给出自定义的示例。

​ 有个形象的公式:HandlerMethodArgumentResolver = HandlerMethod + Argument(参数) + Resolver(解析器)。

​ 解释为:它是HandlerMethod方法的解析器,将HttpServletRequest(header + body 中的内容)解析为HandlerMethod方法的参数(method parameters)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface HandlerMethodArgumentResolver {

/**
* 判断是否支持某种类型的函数参数
*/
boolean supportsParameter(MethodParameter parameter);

/**
* 解析请求中的参数为某个函数的参数对象
*
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

基于这个接口的处理器实现类不可谓不丰富

因为子类众多,所以我分类进行说明。我把它分为四类进行描述:

  1. 基于Name
  2. 数据类型是Map
  3. 固定参数类型
  4. 基于ContentType的消息转换器

基于Name

从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值

这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver来实现,它是最为重要的分支(分类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/**
* 子类需要做如下事:获取方法参数的命名值信息、将名称解析为参数值
* 当需要参数值时处理缺少的参数值、可选地处理解析值
* 特别注意的是:默认值可以使用${}占位符,或者SpEL语句#{}是木有问题的
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;

@Nullable
private final BeanExpressionContext expressionContext;

private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);


public AbstractNamedValueMethodArgumentResolver() {
this.configurableBeanFactory = null;
this.expressionContext = null;
}


public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
// 默认是RequestScope
this.expressionContext =
(beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
}

/**
* 核心方法 注意此方法是final的,并不希望子类覆盖掉他~
*
* @param parameter
* @param mavContainer
* @param webRequest
* @param binderFactory
* @return
* @throws Exception
*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

// 创建 MethodParameter 对应的 NamedValueInfo
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// 支持到了Java 8 中支持的 java.util.Optional
MethodParameter nestedParameter = parameter.nestedIfOptional();

// name属性(也就是注解标注的value/name属性)这里既会解析占位符,还会解析SpEL表达式,非常强大
// 因为此时的 name 可能还是被 ${} 符号包裹, 则通过 BeanExpressionResolver 来进行解析
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}

// 模版抽象方法:将给定的参数类型和值名称解析为参数值。 由子类去实现
// @PathVariable --> 通过对uri解析后得到的decodedUriVariables值(常用)
// @RequestParam --> 通过 HttpServletRequest.getParameterValues(name) 获取(常用)
// @RequestAttribute --> 通过 HttpServletRequest.getAttribute(name) 获取 <-- 这里的 scope 是 request
// @SessionAttribute --> 略
// @RequestHeader --> 通过 HttpServletRequest.getHeaderValues(name) 获取
// @CookieValue --> 通过 HttpServletRequest.getCookies() 获取
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
// 若解析出来值仍旧为null,那就走defaultValue (若指定了的话)
if (arg == null) {
// 可以发现:defaultValue也是支持占位符和SpEL的~~~
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);

// 若 arg == null && defaultValue == null && 非 optional 类型的参数 则通过 handleMissingValue 来进行处理, 一般是报异常
} else if (namedValueInfo.required && !nestedParameter.isOptional()) {

// 它是个protected方法,默认抛出ServletRequestBindingException异常
// 各子类都复写了此方法,转而抛出自己的异常(但都是ServletRequestBindingException的异常子类)
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
// handleNullValue是private方法,来处理null值
// 针对Bool类型有这个判断:Boolean.TYPE.equals(paramType) 就return Boolean.FALSE;
// 此处注意:Boolean.TYPE = Class.getPrimitiveClass("boolean") 它指的基本类型的boolean,而不是Boolean类型哦~~~
// 如果到了这一步(value是null),但你还是基本类型,那就抛出异常了(只有boolean类型不会抛异常哦~)
// 这里多嘴一句,即使请求传值为&bool=1,效果同bool=true的(1:true 0:false) 并且不区分大小写哦(TrUe效果同true)
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
} else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}

// 完成自动化的数据绑定~~~
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// 通过数据绑定器里的Converter转换器把arg转换为指定类型的数值
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
} catch (ConversionNotSupportedException ex) {
// 注意这个异常:MethodArgumentConversionNotSupportedException 类型不匹配的异常
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
} catch (TypeMismatchException ex) {
//MethodArgumentTypeMismatchException是TypeMismatchException 的子类
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}

// protected的方法,本类为空实现,交给子类去复写(并不是必须的)
// 唯独只有PathVariableMethodArgumentResolver把解析处理啊的值存储一下数据到
// HttpServletRequest.setAttribute中(若key已经存在也不会存储了)
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

return arg;
}

/**
* Obtain the named value for the given method parameter.
* 此处有缓存,记录下每一个MethodParameter对象 value是NamedValueInfo值
*/
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
// createNamedValueInfo是抽象方法,子类必须实现
namedValueInfo = createNamedValueInfo(parameter);
// updateNamedValueInfo:这一步就是我们之前说过的为何Spring MVC可以根据参数名封装的方法
// 如果info.name.isEmpty()的话(注解里没指定名称),就通过`parameter.getParameterName()`去获取参数名~
// 它还会处理注解指定的defaultValue:`\n\t\.....`等等都会被当作null处理
// 都处理好后:new NamedValueInfo(name, info.required, defaultValue);(相当于吧注解解析成了此对象嘛~~)
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}

/**
* 抽象方法
*/
protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);


private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
String name = info.name;
if (info.name.isEmpty()) {
name = parameter.getParameterName();
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);
return new NamedValueInfo(name, info.required, defaultValue);
}


@Nullable
private Object resolveStringValue(String value) {
if (this.configurableBeanFactory == null) {
return value;
}
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null || this.expressionContext == null) {
return value;
}
return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
}

/**
* Resolve the given parameter type and value name into an argument value.
* 由子类根据名称,去把值拿出来
*
*/
@Nullable
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
throws Exception;


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());
}


@Nullable
private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
} 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;
}


protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
}


/**
* Represents the information about a named value, including name, whether it's required and a default value.
* protected的内部类 所以所有子类(注解)都是拥有这三个属性值的
*/
protected static class NamedValueInfo {

private final String name;

private final boolean required;

@Nullable
private final String defaultValue;

public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;
}
}
}

该抽象类中定义了解析参数的主逻辑(模版逻辑),子类只需要实现对应的抽象模版方法即可。

对此部分的处理步骤,我把它简述如下:

  1. 基于MethodParameter构建NameValueInfo <– 主要有name, defaultValue, required(其实主要是解析方法参数上标注的注解~)
  2. 通过BeanExpressionResolver(${}占位符以及SpEL) 解析name
  3. 通过模版方法resolveName从 HttpServletRequest, Http Headers, URI template variables 等等中获取对应的属性值(具体由子类去实现)
  4. 对 arg==null这种情况的处理, 要么使用默认值, 若 required = true && arg == null, 则一般报出异常(boolean类型除外~)
  5. 通过WebDataBinder将arg转换成Methodparameter.getParameterType()类型(注意:这里仅仅只是用了数据转换而已,并没有用bind()方法)

该抽象类继承树

从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:

  1. 根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)
  2. 根据方法参数名称name从HttpServletRequest, Http Headers, URI template variables等等中获取属性值
  3. 对arg == null这种情况的处理(非必须)

PathVariableMethodArgumentResolver

它帮助Spring MVC实现restful风格的URL。它用于处理标注有@PathVariable注解的方法参数,用于从URL中获取值(并不是?后面的参数哦)。

并且,并且,并且它还可以解析@PathVariable注解的value值不为空的Map(使用较少,个人不太建议使用)~

UriComponentsContributor

UriComponentsContributor接口:通过查看方法参数和参数值并决定应更新目标URL的哪个部分,为构建UriComponents的策略接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface UriComponentsContributor {

/**
* Whether this contributor supports the given method parameter.
* 此方法完全同HandlerMethodArgumentResolver的这个方法~~~
*/
boolean supportsParameter(MethodParameter parameter);

/**
* 处理给定的方法参数,然后更新UriComponentsbuilder,或者使用uri变量添加到映射中,以便在处理完所有参数后用于扩展uri~~~
*/
void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder,
Map<String, Object> uriVariables, ConversionService conversionService);

}

它的三个实现类:

关于此接口的使用,后面再重点介绍,此处建议自动选择性忽略。

PathVariableMethodArgumentResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {

private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);


/**
* 简单一句话描述:@PathVariable是必须,不管你啥类型
* 标注了注解,且是Map类型,
*
* @param parameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}

/**
* 根据name去拿值的过程非常之简单,但是它和前面的只知识是有关联的
* 至于这个attr是什么时候放进去的,AbstractHandlerMethodMapping.handleMatch()匹配处理器方法上
* 通过UrlPathHelper.decodePathVariables() 把参数提取出来了,然后放进request属性上暂存了~~~
*
*/
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

/**
* MissingPathVariableException是ServletRequestBindingException的子类
*
* @param name
* @param parameter
* @throws ServletRequestBindingException
*/
@Override
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
throw new MissingPathVariableException(name, parameter);
}

/**
* 值完全处理结束后,把处理好的值放进请求域,方便view里渲染时候使用~
* 抽象父类的handleResolvedValue方法,只有它复写了~
*
* @param arg
* @param name
* @param parameter
* @param mavContainer
* @param request
*/
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {

String key = View.PATH_VARIABLES;
int scope = RequestAttributes.SCOPE_REQUEST;
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
if (pathVars == null) {
pathVars = new HashMap<>();
request.setAttribute(key, pathVars, scope);
}
pathVars.put(name, arg);
}

...

}

关于@PathVariable的使用,不用再给例子了。

说明:因为使用路径参数需要进行复杂的匹配流程以及正则匹配,所有效率相较来说低些,若以若是那种对响应事件强要求的(比如记录点击事件…),建议用请求参数代替(当然你也可以重写RequestMappingHandlerMapping的URL匹配方法来定制化你的需求)。
GET /list/cityId/1 属于RESTful /list/cityId?cityId=1不属于RESTful。通过Apache JMeter测试:非RESTful接口的性能是RESTful接口的两倍,接口相应时间上更是达到10倍左右(是–>300ms左右 非–>20ms左右)

RequestParamMethodArgumentResolver

顾名思义,是解析标注有@RequestParam的方法入参解析器,这个注解比上面的注解强大很多了,它用于从请求参数(?后面的)中获取值完成封装。这是我们的绝大多数使用场景。除此之外,它还支持MultipartFile,也就是说能够从MultipartHttpServletRequest | HttpServletRequest 获取数据,并且并且并且还兜底处理没有标注任何注解的“简单类型”~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {

private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);


// 这个参数老重要了:
// true:表示参数类型是基本类型 参考BeanUtils#isSimpleProperty(什么Enum、Number、Date、URL、包装类型、以上类型的数组类型等等)
// 如果是基本类型,即使你不写@RequestParam注解,它也是会走进来处理的~~~(这个@PathVariable可不会哟~)
// fasle:除上以外的。 要想它处理就必须标注注解才行哦,比如List等~
// 默认值是false
private final boolean useDefaultResolution;


/**
* Create a new {@link RequestParamMethodArgumentResolver} instance.
* 此构造只有`MvcUriComponentsBuilder`调用了 传入的false
*
*/
public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
this.useDefaultResolution = useDefaultResolution;
}

/**
* Create a new {@link RequestParamMethodArgumentResolver} instance.
* 传入了ConfigurableBeanFactory ,所以它支持处理占位符${...} 并且支持SpEL了
* 此构造都在RequestMappingHandlerAdapter里调用,最后都会传入true来Catch-all Case 这种设计挺有意思的
*
*/
public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
boolean useDefaultResolution) {

super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}


/**
* 此处理器能处理如下Case:
* 1、所有标注有@RequestParam注解的类型(非Map)/ 注解指定了value值的Map类型(自己提供转换器哦)
* ======下面都表示没有标注@RequestParam注解了的=======
* 1、不能标注有@RequestPart注解,否则直接不处理了
* 2、是上传的request:isMultipartArgument() = true(MultipartFile类型或者对应的集合/数组类型 或者javax.servlet.http.Part对应结合/数组类型)
* 3、useDefaultResolution=true情况下,"基本类型"也会处理
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
} else {
return true;
}
} else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
} else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
} else {
return false;
}
}
}

/**
* 从这也可以看出:即使木有@RequestParam注解,也是可以创建出一个NamedValueInfo来的
*
* @param parameter
* @return
*/
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}

/**
* 核心方法:根据Name 获取值(普通/文件上传)
* 并且还有集合、数组等情况
*
* @param name
* @param parameter
* @param request
* @return
* @throws Exception
*/
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
//获取HttpServletRequest
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
// 这块解析出来的是个MultipartFile或者其集合/数组

if (servletRequest != null) {
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}

Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
// 若解析出来值仍旧为null,那处理完文件上传里木有,那就去参数里取吧
// 由此可见:文件上传的优先级是高于请求参数的
if (arg == null) {
//小知识点:getParameter()其实本质是getParameterNames()[0]的效果
// 强调一遍:?ids=1,2,3 结果是["1,2,3"](兼容方式,不建议使用。注意:只能是逗号分隔)
// ?ids=1&ids=2&ids=3 结果是[1,2,3](标准的传值方式,建议使用)
// 但是Spring MVC这两种都能用List接收 请务必注意他们的区别~~~
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}


// 内部类
private static class RequestParamNamedValueInfo extends NamedValueInfo {

// 请注意这个默认值:如果你不写@RequestParam,那么就会用这个默认值
// 注意:required = false的哟(若写了注解,required默认可是true,请务必注意区分)
// 因为不写注解的情况下,若是简单类型参数都是交给此处理器处理的。所以这个机制需要明白
// 复杂类型(非简单类型)默认是ModelAttributeMethodProcessor处理的
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}

public RequestParamNamedValueInfo(RequestParam annotation) {
super(annotation.name(), annotation.required(), annotation.defaultValue());
}
}

...

}

可以看到这个ArgumentResolver处理器还是很强大的:不仅能处理标注了@RequestParam的参数,还能接收文件上传参数。甚至那些你平时使用中不标注该注解的封装也是它来兜底完成的。至于它如何兜底的,可以参见下面这个骚操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
...
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));
resolvers.add(new ServletModelAttributeMethodProcessor(true));

return resolvers;
}
...
}

可以看到ServletModelAttributeMethodProcessorRequestParamMethodArgumentResolver一样,也是有兜底的效果的。

get请求如何传值数组、集合

如题的这个case太常见了有木有,我们经常会遇到使用get请求向后端需要传值的需求(比如根据ids批量查询)。但到底如何传,URL怎么写,应该是有傻傻分不清楚的不确定的情况。

@PathVariable传参
1
2
3
4
5
6
@ResponseBody
@GetMapping("/test/{objects}")
public Object test(@PathVariable List<Object> objects) {
System.out.println(objects);
return objects;
}

请求URL:/test/fsx,fsx,fsx。控制台打印:

1
[fsx, fsx, fsx]

集合接收成功(使用@PathVariable Object[] objects也是可以正常接收的)。

使用时应注意如下两点:

  1. 多个值只能使用,号分隔才行(否则会被当作一个值,放进数组/集合里,不会报错)
  2. @PathVariable注解是必须的。否则会交给ServletModelAttributeMethodProcessor兜底去处理,它要求有 空构造所以反射创建实例会报错(数组/List)。(注意:如果是这样写ArrayList objects,那是不会报错的,只是值肯定是封装不进来的,一个空对象而已)

    说明:为何逗号分隔的String类型默认就能转化为数组,集合。请参考StringToCollectionConverter/StringToArrayConverter这种内置的GenericConverter通用转换器~~

    @RequestParam传参
    1
    2
    3
    4
    5
    6
    7
    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestParam List<Object> objects) {
    System.out.println(objects);
    return objects;
    }

    请求URL:/test/?objects=1,2,3。控制台打印:

    1
    [1, 2, 3]

    请求URL改为:/test/?objects=1&objects=2&objects=3。控制台打印:

    1
    [1, 2, 3]

    两个请求的URL不一样,但都能正确的达到效果。(@RequestParam Object[] objects这么写两种URL也能正常封装)

    对此有如下这个细节你必须得注意:对于集合List而言@RequestParam注解是必须存在的,否则报错如下(因为交给兜底处理了):

    但如果你这么写String[] objects即使不写注解,也能够正常完成正确封装

    说明:Object[] objects这么写的话不写注解是不行的(报错如上)。至于原因,各位小伙伴可以自行思考,没想明白的话可以给我留言(建议小伙伴一定要弄明白缘由)~

    注意

    ​ 需要注意的是,Spring MVC的这么多HandlerMethodArgumentResolver它的解析是有顺序的:如果多个HandlerMethodArgumentResolver都可以解析某一种类型,以顺序在前面的先解析(后面的就不会再执行解析了)。

    ​ 由于RequestParamMethodArgumentResolver同样可以对Multipart文件上传进行解析,并且默认顺序在RequestPartMethodArgumentResolver之前,所以如果不添加@RequestPart注解,Multipart类型的参数会被RequestParamMethodArgumentResolver解析。

    RequestHeaderMethodArgumentResolver

    @RequestHeader注解,可以把Request请求header部分的值绑定到方法的参数上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {


    public RequestHeaderMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
    super(beanFactory);
    }

    /**
    * 必须标注@RequestHeader注解,并且不能,不能,不能是Map类型
    * 有的小伙伴会说:`@RequestHeader Map headers`这样可以接收到所有的请求头啊
    * 其实不是本类的功劳,是`RequestHeaderMapMethodArgumentResolver`的作用
    *
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return (parameter.hasParameterAnnotation(RequestHeader.class) &&
    !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    RequestHeader ann = parameter.getParameterAnnotation(RequestHeader.class);
    Assert.state(ann != null, "No RequestHeader annotation");
    return new RequestHeaderNamedValueInfo(ann);
    }

    /**
    * 理解起来很简单:可以单值,也可以List/数组
    */
    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    String[] headerValues = request.getHeaderValues(name);
    if (headerValues != null) {
    return (headerValues.length == 1 ? headerValues[0] : headerValues);
    } else {
    return null;
    }
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
    throw new MissingRequestHeaderException(name, parameter);
    }


    private static final class RequestHeaderNamedValueInfo extends NamedValueInfo {

    private RequestHeaderNamedValueInfo(RequestHeader annotation) {
    super(annotation.name(), annotation.required(), annotation.defaultValue());
    }
    }
    }

    此处理器能处理的是我们这么来使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestHeader("Accept-Encoding") String encoding,
    @RequestHeader("Accept-Encoding") List<String> encodingList) {
    System.out.println(encoding);
    System.out.println(encodingList);
    return encoding;
    }

    请求头截图:

    结果打印(集合封装成功了,证明逗号分隔是可以被封装成集合/数组的):

    1
    2
    gzip, deflate, br
    [gzip, deflate, br]

    RequestAttributeMethodArgumentResolver

    处理必须标注有@RequestAttribute注解的参数,原理说这一句话就够了。

    1
    return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);

    SessionAttributeMethodArgumentResolver

    同上(注解不一样,scope不一样而已)

    AbstractCookieValueMethodArgumentResolver

    对解析标注有@CookieValue的做了一层抽象,子类负责从request里拿值(该抽象类不合请求域绑定)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public abstract class AbstractCookieValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

    /**
    * Crate a new {@link AbstractCookieValueMethodArgumentResolver} instance.
    * @param beanFactory a bean factory to use for resolving ${...}
    * placeholder and #{...} SpEL expressions in default values;
    * or {@code null} if default values are not expected to contain expressions
    */
    public AbstractCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
    super(beanFactory);
    }


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(CookieValue.class);
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class);
    Assert.state(annotation != null, "No CookieValue annotation");
    return new CookieValueNamedValueInfo(annotation);
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
    throw new MissingRequestCookieException(name, parameter);
    }

    ... // 并木有实现核心resolveName方法

    }
    ServletCookieValueMethodArgumentResolver

    指定了从HttpServletRequest去拿cookie值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValueMethodArgumentResolver {

    private UrlPathHelper urlPathHelper = new UrlPathHelper();


    public ServletCookieValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
    super(beanFactory);
    }


    public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
    this.urlPathHelper = urlPathHelper;
    }


    @Override
    @Nullable
    protected Object resolveName(String cookieName, MethodParameter parameter,
    NativeWebRequest webRequest) throws Exception {

    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");

    // 工具方法,底层是:request.getCookies()
    Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
    // 如果用javax.servlet.http.Cookie接受值,就直接返回了
    if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
    return cookieValue;
    }
    // 否则返回cookieValue
    else if (cookieValue != null) {
    return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
    }
    else {
    return null;
    }
    }

    }

    一般我们这么来用:

    1
    2
    3
    4
    5
    6
    7
    8
    @ResponseBody
    @GetMapping("/test")
    public Object test(@CookieValue("JSESSIONID") Cookie cookie,
    @CookieValue("JSESSIONID") String cookieValue) {
    System.out.println(cookie);
    System.out.println(cookieValue);
    return cookieValue;
    }

    手动设置一个cookie值,然后请求

    控制台打印如下:

    1
    2
    javax.servlet.http.Cookie@401ef395
    123456

    MatrixVariableMethodArgumentResolver

    标注有@MatrixVariable注解的参数的处理器。Matrix:矩阵,这个注解是Spring3.2新提出来的,增强Restful的处理能力(配合@PathVariable使用),比如这类URL的解析就得靠它:/owners/42;q=11/pets/21;s=23;q=22。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

    public MatrixVariableMethodArgumentResolver() {
    super(null);
    }


    /**
    * MatrixVariable注解是必须的。然后技能处理普通类型,也能处理Map
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
    return false;
    }
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
    MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
    return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
    }
    return true;
    }
    ...
    }

    ExpressionValueMethodArgumentResolver

    它用于处理标注有@Value注解的参数。对于这个注解我们太熟悉不过了,没想到在web层依旧能发挥作用。本文就重点来会会它~

    通过@Value让我们在配置文件里给参数赋值,在某些特殊场合(比如前端不用传,但你想给个默认值,这个时候用它也是一种方案)

    说明:这就相当于在Controller层使用了@Value注解,其实我是不太建议的。因为@Value建议还是只使用在业务层~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {

    /**
    * Create a new {@link ExpressionValueMethodArgumentResolver} instance.
    *
    * @param beanFactory a bean factory to use for resolving ${...}
    * placeholder and #{...} SpEL expressions in default values;
    * or {@code null} if default values are not expected to contain expressions
    * 唯一构造函数 支持占位符、SpEL
    */
    public ExpressionValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
    super(beanFactory);
    }

    /**
    * 必须标注有@Value注解
    *
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(Value.class);
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    Value ann = parameter.getParameterAnnotation(Value.class);
    Assert.state(ann != null, "No Value annotation");
    return new ExpressionValueNamedValueInfo(ann);
    }

    /**
    * 这里恒返回null,因此即使你的key是@Value,也是不会采纳你的传值的哟~
    *
    * @param name
    * @param parameter
    * @param webRequest
    * @return
    * @throws Exception
    */
    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
    // No name to resolve
    return null;
    }

    @Override
    protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
    throw new UnsupportedOperationException("@Value is never required: " + parameter.getMethod());
    }


    private static final class ExpressionValueNamedValueInfo extends NamedValueInfo {
    // 这里name传值为固定值 因为只要你的key不是这个就木有问题
    // required传固定值false
    // defaultValue:取值为annotation.value() --> 它天然支持占位符和SpEL嘛
    private ExpressionValueNamedValueInfo(Value annotation) {
    super("@Value", false, annotation.value());
    }
    }

    }

    根本原理其实只是利用了defaultValue支持占位符和SpEL的特性而已。给个使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 在MVC子容器中导入外部化配置
    @Configuration
    @PropertySource("classpath:my.properties") // 此处有键值对:test.myage = 18
    @EnableWebMvc
    public class WebMvcConfig extends WebMvcConfigurerAdapter { ... }

    @ResponseBody
    @GetMapping("/test")
    public Object test(@Value("#{T(Integer).parseInt('${test.myage:10}') + 10}") Integer myAge) {
    System.out.println(myAge);
    return myAge;
    }

    请求:/test,打印:28。
    注意:若你写成@Value(“#{‘${test.myage:10}’ + 10},那你得到的答案是:1810(成字符串拼接了)。

    另外,我看到网上有不少人说如果把这个@PropertySource(“classpath:my.properties”)放在根容器的config文件里导入,controller层就使用@Value/占位符获取不到值了,其实这是不正确的。理由如下:

    Spring MVC子容器在创建时:initWebApplicationContext()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    if (cwac.getParent() == null) {
    cwac.setParent(rootContext); // 设置上父容器(根容器)
    }

    AbstractApplicationContext:如下代码
    // 相当于子容器的环境会把父容器的Enviroment合并进来
    @Override
    public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
    Environment parentEnvironment = parent.getEnvironment();
    if (parentEnvironment instanceof ConfigurableEnvironment) {
    getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
    }
    }
    }

    AbstractEnvironment:merge()方法如下
    @Override
    public void merge(ConfigurableEnvironment parent) {
    // 完全的从parent里所有的PropertySources里拷贝一份进来
    for (PropertySource<?> ps : parent.getPropertySources()) {
    if (!this.propertySources.contains(ps.getName())) {
    this.propertySources.addLast(ps);
    }
    }
    ...
    }

    这就是为什么说即使你是在根容器里使用的@PropertySource导入的外部资源,子容器也可以使用的原因(因为子容器会把父环境给merge一份过来)。

    参数类型是Map的

    数据来源同上,只是参数类型是Map

    这类解析器我认为是对第一类的有些处理器的一种补充,它依赖上面的相关注解。
    你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader、@PathVariable、@MatrixVariable都赋予了这种能力~

    PathVariableMapMethodArgumentResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
    * 必须标注@PathVariable注解 并且类型是Map,并且注解不能有value值
    * 处理情况和PathVariableMethodArgumentResolver形成了互补
    *
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
    !StringUtils.hasText(ann.value()));
    }

    /**
    * Return a Map with all URI template variables or an empty map.
    * 处理上极其简单,把所有的路径参数使用Map装着返回即可
    */
    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    @SuppressWarnings("unchecked")
    Map<String, String> uriTemplateVars =
    (Map<String, String>) webRequest.getAttribute(
    HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

    if (!CollectionUtils.isEmpty(uriTemplateVars)) {
    return new LinkedHashMap<>(uriTemplateVars);
    } else {
    return Collections.emptyMap();
    }
    }

    }

    RequestParamMapMethodArgumentResolver

    它依赖的方法是:HttpServletRequest#getParameterMap()MultipartRequest#getMultiFileMap()MultipartRequest#getFileMap()等,出现于Spring 3.1

    示例
    1
    2
    3
    4
    5
    6
    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestParam Map<String,Object> params) {
    System.out.println(params);
    return params;
    }

    请求:/test?name=fsx&age=18&age=28。打印

    1
    {name=fsx, age=18}

    从结果看出:

    1. 它不能传一key多值情况
    2. 若出现相同的key,以在最前面的key的值为准。
    3. Map实例是一个LinkedHashMap实例

    RequestHeaderMapMethodArgumentResolver

    一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map

    示例
    1
    2
    3
    4
    5
    6
    @ResponseBody
    @GetMapping("/test")
    public Object test(@RequestHeader Map<String, Object> headers) {
    headers.forEach((k, v) -> System.out.println(k + "-->" + v));
    return headers;
    }

    请求打印:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    host-->localhost:8080
    connection-->keep-alive
    cache-control-->max-age=0
    upgrade-insecure-requests-->1
    user-agent-->Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
    sec-fetch-mode-->navigate
    sec-fetch-user-->?1
    accept-->text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    sec-fetch-site-->none
    accept-encoding-->gzip, deflate, br
    accept-language-->zh-CN,zh;q=0.9
    cookie-->JSESSIONID=123456789

    不过强烈不建议直接使用Map,而是使用HttpHeaders类型。这么写@RequestHeader HttpHeaders headers,获取的时候更为便捷。

    MapMethodProcessor

    它处理Map类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。

    这个处理器同时也解释了:为何你方法入参上写个Map、HashMap、ModelMap等等就可以非常便捷的获取到模型的值的原因~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return Map.class.isAssignableFrom(parameter.getParameterType());
    }

    // 处理逻辑非常简单粗暴:把Model直接返回~~~~
    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    return mavContainer.getModel();
    }
    }

    固定参数类型

    参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream

    这种方式使用得其实还比较多的。比如平时我们需要用Servlet源生的API:HttpServletRequest, HttpServletResponse肿么办? 在Spring MVC内就特别特别简单,只需要在入参上声明:就可以直接使用啦~

    ServletRequestMethodArgumentResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {

    //连Servlet 4.0的PushBuilder都支持了(Spring5.0以上版本支持的)
    @Nullable
    private static Class<?> pushBuilder;

    static {
    try {
    pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
    ServletRequestMethodArgumentResolver.class.getClassLoader());
    } catch (ClassNotFoundException ex) {
    // Servlet 4.0 PushBuilder not found - not supported for injection
    pushBuilder = null;
    }
    }

    // 支持"注入"的类型,可谓多多益善
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
    // webRequest.getNativeRequest(requiredType)
    ServletRequest.class.isAssignableFrom(paramType) ||
    MultipartRequest.class.isAssignableFrom(paramType) ||
    //request.getSession()
    HttpSession.class.isAssignableFrom(paramType) ||
    //PushBuilderDelegate.resolvePushBuilder(request, paramType);
    (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
    //request.getUserPrincipal()
    Principal.class.isAssignableFrom(paramType) ||
    // request.getInputStream()
    InputStream.class.isAssignableFrom(paramType) ||
    //request.getReader()
    Reader.class.isAssignableFrom(paramType) ||
    //HttpMethod.resolve(request.getMethod());
    HttpMethod.class == paramType ||
    //RequestContextUtils.getLocale(request)
    Locale.class == paramType ||
    //RequestContextUtils.getTimeZone(request)
    TimeZone.class == paramType ||
    //RequestContextUtils.getTimeZone(request);
    ZoneId.class == paramType);
    }

    @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
    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));
    }

    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 [" + requiredType.getName() + "]: " + webRequest);
    }
    return nativeRequest;
    }


    @Nullable
    private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
    if (HttpSession.class.isAssignableFrom(paramType)) {
    HttpSession session = request.getSession();
    if (session != null && !paramType.isInstance(session)) {
    throw new IllegalStateException(
    "Current session is not of type [" + paramType.getName() + "]: " + session);
    }
    return session;
    } else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
    return PushBuilderDelegate.resolvePushBuilder(request, paramType);
    } else if (InputStream.class.isAssignableFrom(paramType)) {
    InputStream inputStream = request.getInputStream();
    if (inputStream != null && !paramType.isInstance(inputStream)) {
    throw new IllegalStateException(
    "Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
    }
    return inputStream;
    } else if (Reader.class.isAssignableFrom(paramType)) {
    Reader reader = request.getReader();
    if (reader != null && !paramType.isInstance(reader)) {
    throw new IllegalStateException(
    "Request body reader is not of type [" + paramType.getName() + "]: " + reader);
    }
    return reader;
    } else if (Principal.class.isAssignableFrom(paramType)) {
    Principal userPrincipal = request.getUserPrincipal();
    if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
    throw new IllegalStateException(
    "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
    }
    return userPrincipal;
    } else if (HttpMethod.class == paramType) {
    return HttpMethod.resolve(request.getMethod());
    } else if (Locale.class == paramType) {
    return RequestContextUtils.getLocale(request);
    } else if (TimeZone.class == paramType) {
    TimeZone timeZone = RequestContextUtils.getTimeZone(request);
    return (timeZone != null ? timeZone : TimeZone.getDefault());
    } else if (ZoneId.class == paramType) {
    TimeZone timeZone = RequestContextUtils.getTimeZone(request);
    return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
    }

    // Should never happen...
    throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
    }


    /**
    * Inner class to avoid a hard dependency on Servlet API 4.0 at runtime.
    */
    private static class PushBuilderDelegate {

    @Nullable
    public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) {
    PushBuilder pushBuilder = request.newPushBuilder();
    if (pushBuilder != null && !paramType.isInstance(pushBuilder)) {
    throw new IllegalStateException(
    "Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
    }
    return pushBuilder;

    }
    }

    }

    看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了,又是一种依赖注入的效果体现有木有~

    ServletResponseMethodArgumentResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
    * 它相对来说很比较简单
    *
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    // webRequest.getNativeResponse(requiredType)
    return (ServletResponse.class.isAssignableFrom(paramType) ||
    //response.getOutputStream()
    OutputStream.class.isAssignableFrom(paramType) ||
    //response.getWriter()
    Writer.class.isAssignableFrom(paramType));
    }

    /**
    * Set {@link ModelAndViewContainer#setRequestHandled(boolean)} to
    * {@code false} to indicate that the method signature provides access
    * to the response. If subsequently the underlying method returns
    * {@code null}, the request is considered directly handled.
    */
    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    if (mavContainer != null) {
    mavContainer.setRequestHandled(true);
    }

    Class<?> paramType = parameter.getParameterType();

    // ServletResponse, HttpServletResponse
    if (ServletResponse.class.isAssignableFrom(paramType)) {
    return resolveNativeResponse(webRequest, paramType);
    }

    // ServletResponse required for all further argument types
    return resolveArgument(paramType, resolveNativeResponse(webRequest, ServletResponse.class));
    }

    private <T> T resolveNativeResponse(NativeWebRequest webRequest, Class<T> requiredType) {
    T nativeResponse = webRequest.getNativeResponse(requiredType);
    if (nativeResponse == null) {
    throw new IllegalStateException(
    "Current response is not of type [" + requiredType.getName() + "]: " + webRequest);
    }
    return nativeResponse;
    }

    private Object resolveArgument(Class<?> paramType, ServletResponse response) throws IOException {
    if (OutputStream.class.isAssignableFrom(paramType)) {
    return response.getOutputStream();
    } else if (Writer.class.isAssignableFrom(paramType)) {
    return response.getWriter();
    }

    // Should never happen...
    throw new UnsupportedOperationException("Unknown parameter type: " + paramType);
    }

    }

    SessionStatusMethodArgumentResolver

    支持SessionStatus。值为:mavContainer.getSessionStatus();

    UriComponentsBuilderMethodArgumentResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
    * UriComponentsBuilder/ ServletUriComponentsBuilder
    *
    * @param parameter
    * @return
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> type = parameter.getParameterType();
    return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    Assert.state(request != null, "No HttpServletRequest");
    return ServletUriComponentsBuilder.fromServletMapping(request);
    }

    }

    通过UriComponentsBuilder来得到URL的各个部分,以及构建URL都是非常的方便的。

    RedirectAttributesMethodArgumentResolver

    和重定向属性RedirectAttributes相关。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");

    ModelMap redirectAttributes;
    // 把DataBinder传入到RedirectAttributesModelMap里面去~~~~
    if (binderFactory != null) {
    DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
    redirectAttributes = new RedirectAttributesModelMap(dataBinder);
    } else {
    redirectAttributes = new RedirectAttributesModelMap();
    }
    mavContainer.setRedirectModel(redirectAttributes);
    return redirectAttributes;
    }

    }

    如果涉及到重定向:多个视图见传值,使用它还是比较方便的。

    ModelMethodProcessor

    允许你入参里写:org.springframework.ui.ModelRedirectAttributesRedirectAttributesModelMapConcurrentModelExtendedModelMap等等

    ModelAttributeMethodProcessor

    ModelAttributeMethodProcessor:主要是针对 被 @ModelAttribute注解修饰且不是普通类型(通过 !BeanUtils.isSimpleProperty来判断)的参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

    // 标注有@ModelAttribute它会处理
    // 若没有标注(只要不是“简单类型”),它也会兜底处理
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
    (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }
    }

    基于ContentType消息转换器类型

    我们使用非常频繁的@RequestBody是怎么封装请求体的呢???这块使用非常广泛的地方却还木有讲解到,因为它的处理方式和前面的不太一样。

    利用HttpMessageConverter输入流转换成对应的参数

    AbstractMessageConverterMethodArgumentResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

    /**
    * 默认支持的方法(没有Deleted方法)
    * httpMethod为null 或者方法不属于这集中 或者没有contendType且没有body 那就返回null
    * 也就是说如果是Deleted请求,即使body里有值也是返回null的。(因为它不是SUPPORTED_METHODS )
    */
    private static final Set<HttpMethod> SUPPORTED_METHODS =
    EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);

    private static final Object NO_VALUE = new Object();


    protected final Log logger = LogFactory.getLog(getClass());

    protected final List<HttpMessageConverter<?>> messageConverters;

    protected final List<MediaType> allSupportedMediaTypes;

    // 和RequestBodyAdvice和ResponseBodyAdvice有关的
    private final RequestResponseBodyAdviceChain advice;


    /**
    * Basic constructor with converters only.
    * 构造函数里指定HttpMessageConverter
    */
    public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
    this(converters, null);
    }


    public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters,
    @Nullable List<Object> requestResponseBodyAdvice) {

    Assert.notEmpty(converters, "'messageConverters' must not be empty");
    this.messageConverters = converters;
    // 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
    }



    private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
    Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<>();
    for (HttpMessageConverter<?> messageConverter : messageConverters) {
    allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
    }
    List<MediaType> result = new ArrayList<>(allSupportedMediaTypes);
    MediaType.sortBySpecificity(result);
    return Collections.unmodifiableList(result);
    }


    /**
    * <p>
    * 提供一个defualt方法访问
    */
    RequestResponseBodyAdviceChain getAdvice() {
    return this.advice;
    }

    /**
    * 子类RequestResponseBodyMethodProcessor有复写此方法
    */
    @Nullable
    protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
    Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

    HttpInputMessage inputMessage = createInputMessage(webRequest);
    return readWithMessageConverters(inputMessage, parameter, paramType);
    }
    ...
    }

    说明:此抽象类并没有实现resolveArgument()这个接口方法,而只是提供了一些protected方法,作为工具方法给子类调用,比如最为重要的这个方法:readWithMessageConverters()就是利用消息转换器解析HttpInputMessage的核心。

    它的继承树如下:

    RequestPartMethodArgumentResolver

    它用于解析参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰),数据通过 HttpServletRequest获取

    当属性被标注为@RequestPart的话,那就会经过HttpMessageConverter结合Content-Type来解析,这个效果特别像@RequestBody的处理方式~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

    public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
    super(messageConverters);
    }


    public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
    List<Object> requestResponseBodyAdvice) {

    super(messageConverters, requestResponseBodyAdvice);
    }


    /**
    *
    * 标注了@RequestPart注解的
    * 没有标注@RequestPart并且也没有标注@RequestParam,但是是Multipart类型的也会处理
    */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
    return true;
    }
    else {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
    return false;
    }
    return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
    }
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");

    RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
    boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());

    // 如果注解没有指定,就取形参名
    String name = getPartName(parameter, requestPart);
    parameter = parameter.nestedIfOptional();
    Object arg = null;

    // resolveMultipartArgument这个方法只处理:
    // MultipartFile类型以及对应的数组/集合类型
    // Part类型以及对应的数组集合类型
    // 若形参类型不是以上类型,返回UNRESOLVABLE(空对象)

    // 最终返回StandardMultipartHttpServletRequest/request.getParts()[0]等~
    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
    // 是part类型,那就直接赋值吧
    arg = mpArg;
    }
    // 其它类型
    else {
    try {
    HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
    arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
    if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(request, arg, name);
    if (arg != null) {
    validateIfApplicable(binder, parameter);
    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
    }
    }
    if (mavContainer != null) {
    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    }
    }
    }
    catch (MissingServletRequestPartException | MultipartException ex) {
    if (isRequired) {
    throw ex;
    }
    }
    }

    if (arg == null && isRequired) {
    if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
    throw new MultipartException("Current request is not a multipart request");
    }
    else {
    throw new MissingServletRequestPartException(name);
    }
    }
    return adaptArgumentIfNecessary(arg, parameter);
    }

    private String getPartName(MethodParameter methodParam, @Nullable RequestPart requestPart) {
    String partName = (requestPart != null ? requestPart.name() : "");
    if (partName.isEmpty()) {
    partName = methodParam.getParameterName();
    if (partName == null) {
    throw new IllegalArgumentException("Request part name for argument type [" +
    methodParam.getNestedParameterType().getName() +
    "] not specified, and parameter name information not found in class file either.");
    }
    }
    return partName;
    }

    }

    此处理器用于解析@RequestPart参数类型,它和多部分文件上传有关。关于Spring MVC中的文件上传,此处就不便展开了。后面有个专题专门讲解Spring MVC中的上传、下载~

    ####AbstractMessageConverterMethodProcessor

    命名为Processor说明它既能处理入参,也能处理返回值,当然本文的关注点是方法入参(和HttpMessageConverter相关)。

    请求body体一般是一段字符串/字节流,查询参数可以看做URL的一部分,这两个是位于请求报文的不同地方。

    表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。

    响应body体则是response返回的具体内容,对于一个普通的html页面,body里面就是页面的源代码。对于HttpMessage响应体里可能就是个json串(但无强制要求)。

    响应体一般都会结合Content-Type一起使用,告诉客户端只有知道这个头了才知道如何渲染。

    AbstractMessageConverterMethodProcessor源码稍显复杂,它和Http协议、内容协商有很大的关联:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    implements HandlerMethodReturnValueHandler {

    /* Extensions associated with the built-in message converters */
    //默认情况下:文件们后缀是这些就不弹窗下载
    private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<>(Arrays.asList(
    "txt", "text", "yml", "properties", "csv",
    "json", "xml", "atom", "rss",
    "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));

    private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<>(
    Arrays.asList("audio", "image", "video"));

    private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES =
    Arrays.asList(MediaType.ALL, new MediaType("application"));

    private static final Type RESOURCE_REGION_LIST_TYPE =
    new ParameterizedTypeReference<List<ResourceRegion>>() {
    }.getType();


    // 用于给URL解码 decodingUrlPathHelper.decodeRequestString(servletRequest, filename);
    private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper();
    // rawUrlPathHelper.getOriginatingRequestUri(servletRequest);
    private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper();

    static {
    rawUrlPathHelper.setRemoveSemicolonContent(false);
    rawUrlPathHelper.setUrlDecode(false);
    }


    // 内容协商管理器
    private final ContentNegotiationManager contentNegotiationManager;
    // 扩展名的内容协商策略
    private final PathExtensionContentNegotiationStrategy pathStrategy;

    private final Set<String> safeExtensions = new HashSet<>();


    /**
    * Constructor with list of converters only.
    */
    protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
    this(converters, null, null);
    }

    /**
    * Constructor with list of converters and ContentNegotiationManager.
    * 可以指定内容协商管理器ContentNegotiationManager
    */
    protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
    @Nullable ContentNegotiationManager contentNegotiationManager) {

    this(converters, contentNegotiationManager, null);
    }

    /**
    * Constructor with list of converters and ContentNegotiationManager as well
    * as request/response body advice instances.
    * 这个构造器才是重点
    */
    protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters,
    @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {

    super(converters, requestResponseBodyAdvice);

    // 可以看到:默认情况下会直接new一个
    this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
    // 若管理器里有就用管理器里的,否则new PathExtensionContentNegotiationStrategy()
    this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
    // 用safeExtensions装上内容协商所支持的所有后缀
    // 并且把后缀白名单也加上去(表示是默认支持的后缀)
    this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
    this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
    }

    private static PathExtensionContentNegotiationStrategy initPathStrategy(ContentNegotiationManager manager) {
    Class<PathExtensionContentNegotiationStrategy> clazz = PathExtensionContentNegotiationStrategy.class;
    PathExtensionContentNegotiationStrategy strategy = manager.getStrategy(clazz);
    return (strategy != null ? strategy : new PathExtensionContentNegotiationStrategy());
    }


    /**
    * ServletServerHttpResponse是对HttpServletResponse的包装,主要是对响应头进行处理
    * 主要是处理:setContentType、setCharacterEncoding等等
    * 所以子类若要写数据,就调用此方法来向输出流里写吧~~~
    *
    * @return the output message
    */
    protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    Assert.state(response != null, "No HttpServletResponse");
    return new ServletServerHttpResponse(response);
    }

    /**
    * <p>
    * 注意:createInputMessage()方法是父类提供的,对HttpServletRequest的包装
    * 主要处理了:getURI()、getHeaders()等方法
    * getHeaders()方法主要是处理了:getContentType()..
    */
    protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
    }

    /**
    * Writes the given return type to the given output message.
    * 这个方法省略
    * 这个方法是消息处理的核心之核心:处理了contentType、消息转换、内容协商、下载等等
    * 注意:此处并且还会执行RequestResponseBodyAdviceChain,进行前后拦截
    *
    */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {...
    }
    ...
    }

    本类的核心是各式各样的HttpMessageConverter消息转换器,因为最终的write都是交给它们去完成。

    此抽象类里,它完成了内容协商~

    既然父类都已经完成了这么多事,那么子类自然就非常的简单的。看看它的两个具体实现子类:

    RequestResponseBodyMethodProcessor

    顾名思义,它负责处理@RequestBody这个注解的参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    parameter = parameter.nestedIfOptional();
    // 所以核心逻辑:读取流、消息换换等都在父类里已经完成。子类直接调用就可以拿到转换后的值arg
    // arg 一般都是个类对象。比如Person实例
    Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
    // 若是POJO,就是类名首字母小写(并不是形参名)
    String name = Conventions.getVariableNameForParameter(parameter);

    // 进行数据校验(之前已经详细分析过,此处一笔带过)
    if (binderFactory != null) {
    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    if (arg != null) {
    validateIfApplicable(binder, parameter);
    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
    }
    }

    // 把校验结果放进Model里,方便页面里获取
    if (mavContainer != null) {
    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    }
    }

    // 适配:支持到Optional类型的参数
    return adaptArgumentIfNecessary(arg, parameter);
    }
    }
    HttpEntityMethodProcessor

    用于处理HttpEntityRequestEntity类型的入参的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return (HttpEntity.class == parameter.getParameterType() || RequestEntity.class == parameter.getParameterType());
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException {

    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    // 拿到HttpEntity的泛型类型
    Type paramType = getHttpEntityType(parameter);
    if (paramType == null) {
    // 注意:这个泛型类型是必须指定的,必须的
    throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() + "' in method " + parameter.getMethod() + " is not parameterized");
    }

    // 调用父类方法拿到body的值(把泛型类型传进去了,所以返回的是个实例)
    Object body = readWithMessageConverters(webRequest, parameter, paramType);
    // 注意步操作:new了一个RequestEntity进去,持有实例即可
    if (RequestEntity.class == parameter.getParameterType()) {
    return new RequestEntity<>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI());
    } else { // 用的父类HttpEntity,那就会丢失掉Method等信息(因此建议入参用RequestEntity类型,更加强大些)
    return new HttpEntity<>(body, inputMessage.getHeaders());
    }
    }
    }

    注意:这里可没有validate校验了,这也是经常被面试问到的:使用HttpEntity@RequestBody有什么区别呢?

    从代码里可以直观的看到:有了抽象父类后,子类需要做的事情已经很少了,只需要匹配参数类型、做不同的返回而已。
    关于它俩的使用案例,此处不用再展示了,因为各位平时工作中都在使用,再熟悉不过了。但针对他俩的使用,我总结出如下几个小细节,供以参考:

    1. @RequestBody/HttpEntity它的参数(泛型)类型允许是Map
    2. 方法上的和类上的@ResponseBody都可以被继承,但@RequestBody不可以
    3. @RequestBody它自带有Bean Validation校验能力(当然需要启用),HttpEntity更加的轻量和方便

    HttpEntity/RequestEntity所在包是:org.springframework.http,属于spring-web
    @RequestBody位于org.springframework.web.bind.annotation,同样属于spring-web

    最后还落了一个ErrorsMethodArgumentResolver,在这里补充一下:

    ErrorsMethodArgumentResolver

    它用于在方法参数可以写Errors类型,来拿到数据校验结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return Errors.class.isAssignableFrom(paramType);
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter,
    @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
    @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Assert.state(mavContainer != null,
    "Errors/BindingResult argument only supported on regular handler methods");

    ModelMap model = mavContainer.getModel();
    String lastKey = CollectionUtils.lastElement(model.keySet());

    // 只有@RequestBody/@RequestPart注解的 这里面才会有值
    if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
    return model.get(lastKey);
    }

    // 简单的说:必须有@RequestBody/@RequestPart这注解标注,Errors参数才有意义
    throw new IllegalStateException(
    "An Errors/BindingResult argument is expected to be declared immediately after " +
    "the model attribute, the @RequestBody or the @RequestPart arguments " +
    "to which they apply: " + parameter.getMethod());
    }
    }

    Spring MVC参数处理器的注册与顺序

    到这里,一个不落的把Spring MVC内置提供的参数处理器ArgumentResolver说了个遍。
    前面我有提到过:参数处理对处理器的顺序是敏感的,因此我们需要关注Spring MVC最终的执行顺序,这时候我们的聚合容器HandlerMethodArgumentResolverComposite就出场了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
    // 具有缓存
    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
    ...
    // @since 4.3 木有任何地方调用
    public void clear() {
    this.argumentResolvers.clear();
    }

    // getArgumentResolver()方法是本文的核心
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
    }
    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    // 这里是关键:每个参数最多只会被一个处理器处理
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
    throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + " supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

    ...

    // 这块逻辑保证了每个parameter参数最多只会被一个处理器处理
    // 这个从缓存的数据结构中也能够看出来的
    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
    for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
    if (methodArgumentResolver.supportsParameter(parameter)) {
    result = methodArgumentResolver;
    this.argumentResolverCache.put(parameter, result);
    break;
    }
    }
    }
    return result;
    }
    }

    缺省情况Spring MVC注册的处理器(顺序)如下:

    它初始化处的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    RequestMappingHandlerAdapter:
    @Override
    public void afterPropertiesSet() {
    ...
    // 26个,详见方法getDefaultArgumentResolvers
    if (this.argumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 12个 详见方法getDefaultInitBinderArgumentResolvers
    if (this.initBinderArgumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
    this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    ...
    }

    注意:这里面initBinderArgumentResolvers最终只会有12个处理器,因为它的注册方法如下(也是这个顺序):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
    resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));

    return resolvers;
    }

    ​ 前面有提到过说标注有@InitBInder注解里也可以写很多类型的参数,但因为它只会有12个处理器,所以有些参数它是不能写的(比如@RequestBodyErrors等等这种都是不能写的),不用一一枚举,做到心中有数就成。

    评论