自定义类型参数的数据绑定源码分析
自定义类型参数的数据绑定源码分析
关于自定义类型参数的参数解析场景,SpringMVC处理请求源码分析 —— part2 场景分析 > 2. 表单提交 > 2.1 源码分析 已经讲得非常清晰了,下面主要是对该博客中的内容做一些补充。
前端页面表单:
1 | <form action="/saveuser" method="post"> |
后端自定义类型 Person
以及 Pet
类定义:
1 |
|
后端处理器方法:
1 |
|
ServletModelAttributeMethodProcessor 的数据绑定原理
ServletModelAttributeMethodProcessor
参数解析实际调用的是其父类 ModelAttributeMethodProcessor#resolveArgument
方法,下面简化选出了其中的关键步骤:
1 | // ModelAttributeMethodProcessor.java |
当前 parameter
封装的是 Person saveuser(Person person)
中 Person
参数的信息,参数解析时,会先通过反射调用 Person
的无参构造器,创建 Person
壳对象/空对象。
接着,为了将请求 webRequest
中的请求参数数据,绑定到刚刚创建的 Person
壳对象的属性中,需要数据绑定工厂生产出一个数据绑定器 binder
来帮我们完成这项工作(数据绑定工厂在生产时,会将其内部的转换服务,装配给所生产出的数据绑定器,从而数据绑定器具备数据转换能力)。
然后,数据绑定器解析得到请求中的数据,并将数据转换为恰当的格式后,绑定到 Person
壳对象的属性中。
数据绑定工作完成后,还会将已完成绑定的 Person
对象,以及本次绑定结果,都放到 mavContainer
中。
所以重点在于 bindRequestParameters(binder, webRequest)
,往下看:
1 | // ServletModelAttributeMethodProcessor.java |
重点在于 servletBinder.bind(servletRequest)
,往下看:
1 | // ServletRequestDataBinder.java |
数据绑定器首先对请求参数数据进行解析,将其转换为 MutablePropertyValues
。
MutablePropertyValues
内部保存了一个 List<PropertyValue>
,PropertyValue
能够存储一个键值对,List
中每个 PropertyValue
就相应封装了请求参数数据中的一个 KV 对。
比如请求数据是 userName=tom&age=18
,那么就会转换得到两个 PropertyValue
:PropertyValue1(name="userName", value="tom")
,PropertyValue2(name="age", value="18")
。
底层是如何进行转换的呢?其实是 request.getParameterNames()
调用原生 API 拿到所有请求参数的参数名,然后遍历每个参数名,调用 request.getParameterValues()
来获取值,拿到参数名和参数值以后,就可以封装为一个 PropertyValue
。
数据绑定操作重点在于 doBind(mpvs)
,往下看:
1 | // DataBinder.java |
重点在于 applyPropertyValues(mpvs)
,往下看:
1 | // DataBinder.java |
getPropertyAccessor()
会返回一个 BeanWrapperImpl
。
BeanWrapperImpl
见名知义,它就是一个包装对象,其内部封装了一个 JavaBean 对象(这里封装的是 Person
壳对象),它主要有两个功能:
- 基于反射提供了一系列功能强大的函数,能够很方便地对被包装的 JavaBean 对象的属性进行各种访问和设置(比如支持级联属性的访问和设置)
- 内部包含了转换服务,在操作属性时,提供类型转换功能。
getPropertyAccessor()
函数所做的工作是,找到 this.target
所指向的 Person
壳对象,使用 BeanWrapperImpl
对其进行包装,将数据绑定器内部的转换服务 conversionService
装配给 BeanWrapperImpl
。
这样后续我们就能直接调用 BeanWrapperImpl#setPropertyValues
来对被包装的 Person
壳对象的属性赋值了。
我们深入 setPropertyValues
看看:
1 |
|
可以看到是取出了之前准备好的 List<PropertyValue>
,然后逐个 PropertyValue
调用 setPropertyValue(pv)
进行 Person
壳对象的属性绑定。
setPropertyValue(pv)
底层无非就是对值进行类型转换,然后通过反射调用 setter 进行属性值的设置。
进入 setPropertyValue
看看:
1 |
|
getPropertyAccessorForPropertyPath(propertyName)
见名知义,就是根据属性名路径,确定对应的属性访问器,也即确定对应的 BeanWrapperImpl
。
当 propertyName
值为 userName
时,该方法获取到的 BeanWrapperImpl
就是 this
!注意,当前 setPropertyValue
就是封装了 Person
壳对象的 BeanWrapperImpl
内部的方法,Person
壳对象中有 userName
属性。
当 propertyName
值为级联属性 pet.age
时,该方法获取到的 BeanWrapperImpl
就不是 this
了,而是一个 Pet
壳对象的 BeanWrapperImpl
。无论是 Pet
壳对象,还是包装了 Pet
壳对象的 BeanWrapperImpl
,都是在该方法底层刚刚处理 pet.age
时创建的。
有兴趣的话,可以深入 getPropertyAccessorForPropertyPath
源码底层进行学习。
根据属性路径,拿到对应的属性访问器 nestedPa
后,就可以执行 nestedPa.setPropertyValue(tokens, pv)
来做属性绑定了:
1 | protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { |
数据绑定器相关的 SpringMVC 自动配置
数据绑定器是数据绑定器工厂生产出来的。
1 | WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); |
数据绑定器工厂是在 RequestMappingHandlerAdapter#invokeHandlerMethod
中构造出来的,该工厂封装了 RequestMappingHandlerAdapter
中的 WebBindingInitializer
。
1 |
|
WebBindingInitializer
是一个接口,它只有一个实现类 ConfigurableWebBindingInitializer
,实现类内部封装了转换服务 private ConversionService conversionService
,数据绑定器中的转换服务就是一路从这里拿到的。
ConfigurableWebBindingInitializer
并没有直接注册在容器中!SpringMVC 的自动配置类中,是在注册 RequestMappingHandlerAdapter
时,创建了 ConfigurableWebBindingInitializer
对象并设置到 RequestMappingHandlerAdapter
中。
此外,SpringMVC 自动配置类还注册了转换服务 ConversionService
到容器中。