Spring MVC参数绑定过程

Posted by Mingo's Blog on 2015-07-11 00:00

对于http://domain/path1/path2/mothod?k1=v1&k2=v2这种请求

对应的Controller方法为

1
2
3
4
5
@RequestMapping("/path1/path2/mothod")
// model中含有k2属性
public ModelAndView mothod( String k1, Model model) {
  return null;
}

那么Spring是如何把k1, k2的值是如何注入到相应的位置上呢?

Spring源码在:org.springframework.web.method.support.InvocableHandlerMethod

执行请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, 
	Object... providedArgs) throws Exception {
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

	if (logger.isTraceEnabled()) {
		StringBuilder builder = new StringBuilder("Invoking [");
		builder.append(this.getMethod().getName()).append("] method with arguments ");
		builder.append(Arrays.asList(args));
		logger.trace(builder.toString());
	}

	Object returnValue = invoke(args);

	if (logger.isTraceEnabled()) {
		logger.trace("Method [" + this.getMethod().getName() + "] returned [" + returnValue + "]");
	}

	return returnValue;
}

解析参数列表

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
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(parameterNameDiscoverer);
		GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());

		args[i] = resolveProvidedArgument(parameter, providedArgs);
		if (args[i] != null) {
			continue;
		}

		if (argumentResolvers.supportsParameter(parameter)) {
			try {
				args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, 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;
}

getMethodParameters(); 方法返回对应的Controller实现方法的参数列表, 然后对每个参数绑定(注入)数据 代码关键在于:args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);

获取每个参数值

可以看到, 对每种类型的参数, 获取方式在org.springframework.web.method.support.HandlerMethodArgumentResolverComposite,

1
2
3
4
5
6
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
	NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

可以看到每种参数类型的处理逻辑是不同的, Spring通过HandlerMethodArgumentResolver接口实现了常见类型的处理逻辑 对于基本类型, 枚举类型调用: org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver实现 代码很简单, 基本上就是

1
2
3
4
String[] paramValues = webRequest.getParameterValues(name);
if (paramValues != null) {
	arg = paramValues.length == 1 ? paramValues[0] : paramValues;
}

对于自定义model调用: org.springframework.web.method.annotation.ModelAttributeMethodProcessor, org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter, 
	WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
	String value = getRequestValueForAttribute(attributeName, request);
	if (value != null) {
		Object attribute = createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);
		if (attribute != null) {
			return attribute;
		}
	}

	return super.createAttribute(attributeName, parameter, binderFactory, request);
}

先从request中取出属性值, 如果不为空且有自定义类型转换服务, 把request中的返回的字符串按用户自定的类型转换实现进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
protected Object createAttributeFromRequestValue(String sourceValue, String attributeName, 
	MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
	DataBinder binder = binderFactory.createBinder(request, null, attributeName);
	ConversionService conversionService = binder.getConversionService();
	if (conversionService != null) {
		TypeDescriptor source = TypeDescriptor.valueOf(String.class);
		TypeDescriptor target = new TypeDescriptor(parameter);
		if (conversionService.canConvert(source, target)) {
			return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
		}
	}
	return null;
}

如果为空, 则调用默认的数据绑定逻辑进行参数注入

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 final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, 
	NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception {
	String name = ModelFactory.getNameForParameter(parameter);
	Object attribute = (mavContainer.containsAttribute(name)) ?
		mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);

	WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
	if (binder.getTarget() != null) {
		bindRequestParameters(binder, request);
		validateIfApplicable(binder, parameter);
		if (binder.getBindingResult().hasErrors()) {
			if (isBindExceptionRequired(binder, parameter)) {
				throw new BindException(binder.getBindingResult());
			}
		}
	}

	// Add resolved attribute and BindingResult at the end of the model

	Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
	mavContainer.removeAttributes(bindingResultModel);
	mavContainer.addAllAttributes(bindingResultModel);

	return binder.getTarget();
}

其中bindRequestParameters(binder, request);是关键

自定义类型转换

Spring3的类型转换接口实现逻辑如下:

1
2
3
4
5
6
7
8
public class AndroidLoginContextConverter implements Converter<String, AndroidLoginContext> {

	@Override
	public AndroidLoginContext convert( String source ) {
		return JSON.parseObject( source, AndroidLoginContext.class );
	}

}

在context.xml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
	<property name="converters">
	       	<list>
	            	<bean class="com.meizu.reader.web.android.AndroidLoginContextConverter"/>  
	        </list>
    	</property>
</bean>
	
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">  
    	<property name="conversionService" ref="conversionService"/>  
</bean>
	
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">  
	<property name="webBindingInitializer" ref="webBindingInitializer"/>  
</bean>

然后对request需要做包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AndroidUserinfoRequestWrapper extends HttpServletRequestWrapper {
  	@Override
	public String getParameter(String name) {
		if( "androidLoginContext".equals( name ) ) {
			return JSON.toJSONString( login );
		}
		
		String[] values = this.getParameterValues(name);
		if (values == null) {
			return null;
		}
		else {
			return values[0];
		}
	}
}

其中如果参数类型为model对象, 参数名为类名的SimpleName, 首字母小写 如果为数据或者为集合, 则为类名的SimpleName+List