ModelAttributeMethodProcessor参数解析过程
ModelAttributeMethodProcessor
对参数的解析过程如下。ModelAttributeMethodProcessor
主要 针对加@ModelAttribute
注解的参数,和非简单对象类型(哪些类型属于简单对象类型,在后文中会给出说明)的参数进行解析处理。
ModelAttributeMethodProcessor#resolveArgument
:参数解析的入口方法ModelAttributeMethodProcessor#constructAttribute
:创建参数对象,通过获取参数类型的构造函数,然后通过反射机制,用构造函数实例化一个参数对象ModelAttributeMethodProcessor#bindRequestParameters
:绑定参数对象的值
入口方法resolveArgument
的核心源码如下:
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
......
if (bindingResult == null) {
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, type);
if (attribute == null) {
constructAttribute(binder, webRequest);
attribute = wrapAsOptionalIfNecessary(parameter, binder.getTarget());
}
if (!binder.getBindingResult().hasErrors()) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
}
......
}
......
}
ModelAttributeMethodProcessor
会通过ModelAttributeMethodProcessor#constructAttribute
方法来创建参数对象。从参数名称上也能看出来是通过构造函数来创建实例的。而这个方法最终会通过 DataBinder#createObject
方法来创建对象。部分代码如下
@Nullable
private Object createObject(ResolvableType objectType, String nestedPath, ValueResolver valueResolver) {
Class<?> clazz = objectType.resolve();
boolean isOptional = (clazz == Optional.class);
clazz = (isOptional ? objectType.resolveGeneric(0) : clazz);
if (clazz == null) {
throw new IllegalStateException(
"Insufficient type information to create instance of " + objectType);
}
Object result = null;
Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
......
}
报错的代码在Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
,因为Set
是一个接口,没有构造函数,因此无法获取到其构造函数,所以这里会报错。
那如果换成HashSet
是不是就可以了呢?当然不行!!换成HashSet
后创建对象的步骤虽然不会报错,但是集合内没有任何数据,也即只是创建了一个空对象。
因为实例创建成功后会调用实例创建后会调用org.springframework.web.method.annotation.ModelAttributeMethodProcessor#bindRequestParameters
来绑定参数值,这样虽然HashSet
对象能创建成功,但是绑定参数是通过读取对象中的字段,然后通过字段的Setter
方法来给对象中的字段赋值的。而HashSet
中的没有对应的字段。因此虽然不会报错,但是参数对象中是空的,没有任何元素。
ModelAttributeMethodProcessor#bindRequestParameters
方法最终通过DataBinder#applyPropertyValues
方法为对象的字段赋值。具体流程如下:
下面略了解下代码
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;
servletBinder.bind(servletRequest);
}
org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor#bindRequestParameters
方法中调用了servletBinder.bind
方法。
public void bind(ServletRequest request) {
if (!this.shouldNotBindPropertyValues()) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
} else if (isFormDataPost(request)) {
HttpServletRequest httpServletRequest = (HttpServletRequest)WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, this.isBindEmptyMultipartFiles());
}
}
this.addBindValues(mpvs, request);
this.doBind(mpvs);
}
}
servletBinder.bind
方法是一个核心方法,在这个方法中组装了一个MutablePropertyValues
对象。也即将request
中所有的参数取出,然后封装成一个MutablePropertyValues
对象,用于在后面为对象赋值。
最终通过DataBinder#applyPropertyValues
方法为对象的字段赋值。
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
getPropertyAccessor()
方法返回的对象类型是org.springframework.beans.BeanWrapperImpl
。BeanWrapperImpl
又是AbstractPropertyAccessor
的子类。setPropertyValues
方法其实是AbstractPropertyAccessor
的方法。
在setPropertyValues
方法中为字段赋值时,会对字段进行类型转换,而类型转换最终是通过ConversionService
来做的。具体调用过程如下:
在Spring所有的Converter
中,有一个StringToCollectionConverter
是专门处理字符串到集合类型的转换的。
我们来一个简单的总结
ModelAttributeMethodProcessor
来处理参数为非简单对象类型的参数。- 先通过参数类型实例化一个参数对象。
- 通过读取
request
中的参数,通过反射机制将字段的值set
到对象内。 - 如果字段类型不是字符串,会通过
ConversionService
对类型进行转换。
以上便是ModelAttributeMethodProcessor
的核心功能。
ConversionService
在SpringMVC中是最重要的类之一。在Spring中大多情况下,请求和应答的类型转换都通过它进行。而另一个涉及类型转换的很重要的类是MessageConverter
。
那你是否想到了怎么在不加@RequestParam
注解的前提下,接收Set
类型的参数了吗?
那就是定义一个对象,对象中有一个Set
类型字段,而字段的名称为请求参数的名称即可。如下:
@Data
public class SetParam {
private Set<String> paramSet;
}
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/setParam")
public ResponseBean setTest(SetParam params) {
System.out.println(params);
return new ResponseBean(true, "OK");
}
}