Spring MVC 请求参数绑定

在做Spring MVC时,我们只需用@Controllor来标记Controllor的bean,再用@RequestMapping来标记需要接受请求的方法,就可以根据网页请求中的参数名,自动绑定到POJO对象的属性名,这是相当方便的。其中的原理是什么呢?

前瞻

通过Spring MVC请求调用链,实际是由ServletInvocableHandlerMethod.invokeAndHandle(webRequest, mavContainer)方法执行处理。

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
/**
* Invokes the method and handles the return value through a registered
* {@link HandlerMethodReturnValueHandler}.
*
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
*/
public final void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
// 执行请求方法,返回结果。。。
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}

webRequesthttpRequest包装后的类,添加了uri成员,标记请求uri以便方便分配。mavContainerModelAndViewContainer类对象,用于绑定模块(POJO)和视图(view即网页)。

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
/**
* Invoke the method after resolving its argument values in the context of the given request. <p>Argument
* values are commonly resolved through {@link HandlerMethodArgumentResolver}s. The {@code provideArgs}
* parameter however may supply argument values to be used directly, i.e. without argument resolution.
* Examples of provided argument values include a {@link WebDataBinder}, a {@link SessionStatus}, or
* a thrown exception instance. Provided argument values are checked before argument resolvers.
* @param request the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type, not resolved
* @return the raw value returned by the invoked method
* @exception Exception raised if no suitable argument resolver can be found, or the method raised an exception
*/
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 绑定参数,重点。。。。。
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Invoking [");
sb.append(getBeanType().getSimpleName()).append(".");
sb.append(getMethod().getName()).append("] method with arguments ");
sb.append(Arrays.asList(args));
logger.trace(sb.toString());
}
Object returnValue = invoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}

invokeForRequest方法中,Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);该方法获取每个请求参数与对应的值

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
/**
* Get the method argument values for the current request.
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取所有参数所属的类
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
private String getArgumentResolutionErrorMessage(String message, int index) {
MethodParameter param = getMethodParameters()[index];
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
return getDetailedErrorMessage(message);
}

这个方法也还是比较简单的,循环遍历请求参数,然后委托给HandlerMethodArgumentResolver接口的方法 resolveArgument去进行参数值解析。

绑定参数名

这里面对ParameterNameDiscovery初始化,用来查找参数名:

1
2
3
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
// 。。。。。。省略
methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);

低于java1.8使用new LocalVariableTableParameterNameDiscoverer()来解析参数名。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String[] getParameterNames(Method method) {
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
Class<?> declaringClass = originalMethod.getDeclaringClass();
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
if (map == null) {
map = inspectClass(declaringClass);
this.parameterNamesCache.put(declaringClass, map);
}
if (map != NO_DEBUG_INFO_MAP) {
return map.get(originalMethod);
}
return null;
}

通过map = inspectClass(declaringClass);获取名称map。

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
/**
* Inspects the target class. Exceptions will be logged and a maker map returned
* to indicate the lack of debug information.
*/
private Map<Member, String[]> inspectClass(Class<?> clazz) {
InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
if (is == null) {
// We couldn't load the class file, which is not fatal as it
// simply means this method of discovering parameter names won't work.
if (logger.isDebugEnabled()) {
logger.debug("Cannot find '.class' file for class [" + clazz +
"] - unable to determine constructor/method parameter names");
}
return NO_DEBUG_INFO_MAP;
}
try {
ClassReader classReader = new ClassReader(is);
Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>(32);
classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
return map;
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Exception thrown while reading '.class' file for class [" + clazz +
"] - unable to determine constructor/method parameter names", ex);
}
}
catch (IllegalArgumentException ex) {
if (logger.isDebugEnabled()) {
logger.debug("ASM ClassReader failed to parse class file [" + clazz +
"], probably due to a new Java class file version that isn't supported yet " +
"- unable to determine constructor/method parameter names", ex);
}
}
finally {
try {
is.close();
}
catch (IOException ex) {
// ignore
}
}
return NO_DEBUG_INFO_MAP;
}

ClassReader位于org.springframework.asm包中,是Spring用于反编译的包,读取class信息,class信息中是包含参数名的(可以用文本编辑器打开一个class文件查看,虽然有乱码,但是方法的参数名还在)。

通过accept填充map对象,map的键为成员名(方法名或者参数名),值为参数列表(字符串数组)。

由此可见,Spring是直接读取class文件来读取参数名的.

注意:this.parameterNamesCache.put(declaringClass, map);使用了ConcurrentHashMap设置方法名缓存。避免每一次调用都要反编译class文件。

解析参数值

要给参数注入参数的值,最终是通过HandlerMethodArgumentResolver接口的实现类HandlerMethodArgumentResolverComposite实现的。HandlerMethodArgumentResolverComposite类实现是采用的组合模式的设计策略,可以在类RequestMappingHandlerAdapter中的方法afterPropertiesSet方法中看到HandlerMethodArgumentResolverComposite中的resovler的初始化代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
initControllerAdviceCache();
}

通过这个afterPropertiesSet方法增加默认的参数解析器,默认的参数解析器如下:

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
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// Annotation-based argument resolution
// 支持带有@RequestParam注解的参数
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 ServletModelAttributeMethodProcessor(false));
// @RequestBody注解的参数解析器
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// Type-based argument resolution
// 参数类型是实现或继承或是WebRequest、ServletRequest这些类。
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}

常用的参数解析器由RequestParamMethodArgumentResolverRequestResponseBodyMethodProcessor(这个类也实现了HandlerMethodReturnValueHandler接口)等。

参数解析源码说明如下:

  • 如果有提供的参数值,直接返回提供的参数值
1
2
3
4
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
  • 检察参数类型是否支持
1
this.argumentResolvers.supportsParameter(parameter)
1
2
3
4
// HandlerMethodArgumentResolverComposite类
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
*/
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
parameter.getGenericParameterType() + "]");
}
// 检察参数类型是否支持(每一个HandlerMethodArgumentResolver都有一个supportsParameter方法判断是否支持参数类型)
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
  • 查询参数解析器,找到后委托给参数解析器解析
1
2
3
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
1
2
3
4
5
6
7
8
9
10
11
12
// HandlerMethodArgumentResolver实现类(这里是RequestResponseBodyMethodProcessor)
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}
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
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
// 查看参数是否有@RequestBody注解
RequestBody ann = methodParam.getParameterAnnotation(RequestBody.class);
if (!ann.required()) {
InputStream inputStream = inputMessage.getBody();
if (inputStream == null) {
return null;
}
else if (inputStream.markSupported()) {
inputStream.mark(1);
if (inputStream.read() == -1) {
return null;
}
inputStream.reset();
}
else {
final PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
return null;
}
else {
pushbackInputStream.unread(b);
}
inputMessage = new ServletServerHttpRequest(servletRequest) {
@Override
public InputStream getBody() {
// Form POST should not get here
return pushbackInputStream;
}
};
}
}
return super.readWithMessageConverters(inputMessage, methodParam, paramType);
}

然后会执行父类(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

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
/**
* Create the method argument value of the expected parameter type by reading
* from the given HttpInputMessage.
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param methodParam the method argument
* @param targetType the type of object to create, not necessarily the same as
* the method parameter type (e.g. for {@code HttpEntity<String>} method
* parameter the target type is String)
* @return the created method argument value
* @throws IOException if the reading from the request fails
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
*/
@SuppressWarnings("unchecked")
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage,
MethodParameter methodParam, Type targetType) throws IOException, HttpMediaTypeNotSupportedException {
MediaType contentType;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class<?> contextClass = methodParam.getDeclaringClass();
Map<TypeVariable, Type> map = GenericTypeResolver.getTypeVariableMap(contextClass);
Class<T> targetClass = (Class<T>) GenericTypeResolver.resolveType(targetType, map);
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter genericConverter = (GenericHttpMessageConverter) converter;
if (genericConverter.canRead(targetType, contextClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + targetType + "] as \"" +
contentType + "\" using [" + converter + "]");
}
return genericConverter.read(targetType, contextClass, inputMessage);
}
}
if (targetClass != null) {
if (converter.canRead(targetClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + targetClass.getName() + "] as \"" +
contentType + "\" using [" + converter + "]");
}
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
}
}
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}

这里是用了消息转换器HttpMessageConverter

  • 如果未找到抛出异常
1
2
3
4
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}

参考

反射获取一个方法中的参数名(不是类型)

Spring源码研究:数据绑定

热评文章